/** * This file is part of the NocoBase (R) project. * Copyright (c) 2020-2024 NocoBase Co., Ltd. * Authors: NocoBase Team. * * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ import { render, waitFor } from '@testing-library/react'; import React from 'react'; import { vi } from 'vitest'; import { FlowEngine } from '../../flowEngine'; import { FlowModel } from '../../models/flowModel'; import { FlowEngineProvider } from '../../provider'; import { FlowModelRenderer } from '../FlowModelRenderer'; describe('FlowModelRenderer', () => { let flowEngine: FlowEngine; let model: FlowModel; beforeEach(() => { flowEngine = new FlowEngine(); model = new FlowModel({ uid: 'test-model', flowEngine, }); // Mock dispatchEvent to track calls model.dispatchEvent = vi.fn().mockResolvedValue([]); // Mock render to return something model.render = vi.fn().mockReturnValue(
Model Content
); }); const renderWithProvider = (ui: React.ReactNode) => { return render({ui}); }; test('should pass useCache to useApplyAutoFlows and set it on context', async () => { const { unmount } = renderWithProvider(); await waitFor(() => { expect(model.dispatchEvent).toHaveBeenCalledTimes(1); expect(model.dispatchEvent).toHaveBeenCalledWith( 'beforeRender', undefined, expect.objectContaining({ useCache: true }), ); }); // Check if useCache is set on context expect(model.context.useCache).toBe(true); unmount(); }); test('should pass useCache=false to useApplyAutoFlows and set it on context', async () => { const { unmount } = renderWithProvider(); await waitFor(() => { expect(model.dispatchEvent).toHaveBeenCalledTimes(1); expect(model.dispatchEvent).toHaveBeenCalledWith( 'beforeRender', undefined, expect.objectContaining({ useCache: false }), ); }); expect(model.context.useCache).toBe(false); unmount(); }); test('should not pass useCache if not provided', async () => { const { unmount } = renderWithProvider(); await waitFor(() => { expect(model.dispatchEvent).toHaveBeenCalledTimes(1); expect(model.dispatchEvent).toHaveBeenCalledWith( 'beforeRender', undefined, expect.objectContaining({ useCache: undefined }), ); }); // context.useCache should be undefined (or default) expect(model.context.useCache).toBeUndefined(); unmount(); }); test('should clear stale beforeRender state after unmount when reusing the same model', async () => { const statefulEngine = new FlowEngine(); const onMountSpy = vi.fn(); const onUnmountSpy = vi.fn(); class StatefulModel extends FlowModel { render(): any { return
Stateful Content
; } protected onMount(): void { onMountSpy(); } protected onUnmount(): void { onUnmountSpy(); } } const statefulModel = new StatefulModel({ uid: 'stateful-model', flowEngine: statefulEngine, }); const executorSpy = vi.spyOn((statefulEngine as any).executor, 'dispatchEvent').mockResolvedValue([]); const firstRender = renderWithProvider(); await waitFor(() => { expect(executorSpy).toHaveBeenCalledTimes(1); }); await waitFor(() => { expect(onMountSpy).toHaveBeenCalledTimes(1); }); firstRender.unmount(); await waitFor(() => { expect(onUnmountSpy).toHaveBeenCalledTimes(1); }); executorSpy.mockClear(); statefulModel.setStepParams('anyFlow', 'anyStep', { x: 1 }); await new Promise((resolve) => setTimeout(resolve, 150)); expect(executorSpy.mock.calls.length).toBe(0); const secondRender = renderWithProvider(); await waitFor(() => { expect(executorSpy).toHaveBeenCalledTimes(1); }); await waitFor(() => { expect(onMountSpy).toHaveBeenCalledTimes(2); }); const [target, eventName, inputArgs, options] = executorSpy.mock.calls[0]; expect(target).toBe(statefulModel); expect(eventName).toBe('beforeRender'); expect(inputArgs).toBeUndefined(); expect(options).toMatchObject({ useCache: true }); secondRender.unmount(); await waitFor(() => { expect(onUnmountSpy).toHaveBeenCalledTimes(2); }); }); });