/** * 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 React from 'react'; import { render } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; import { useDialog } from '../useDialog'; import { FlowContext } from '../../flowContext'; // Mock dependencies vi.mock('../../provider', () => ({ FlowEngineProvider: ({ children }) => children, })); vi.mock('../../FlowContextProvider', () => ({ FlowViewContextProvider: ({ children }) => children, })); vi.mock('../../ViewScopedFlowEngine', () => ({ createViewScopedEngine: (engine) => ({ context: new FlowContext(), unlinkFromStack: vi.fn(), setDestroyView: vi.fn(), // mimic real view stack linkage: previousEngine points to the last engine in chain previousEngine: (engine as any)?.nextEngine || engine, }), })); vi.mock('../../utils/variablesParams', () => ({ createViewRecordResolveOnServer: vi.fn(), getViewRecordFromParent: vi.fn(), })); vi.mock('../DialogComponent', () => ({ default: ({ children }) =>
{children}
, })); // Mock usePatchElement to return a mock close function const mockCloseFunc = vi.fn(); const mockPatchElement = vi.fn(() => mockCloseFunc); vi.mock('../usePatchElement', () => ({ default: () => [[], mockPatchElement], })); describe('useDialog - close/destroy logic', () => { const createMockFlowContext = () => { const ctx = new FlowContext(); ctx.defineMethod('t', (key: string) => key); ctx.engine = { context: new FlowContext(), emitter: { emit: vi.fn(), }, }; return ctx; }; beforeEach(() => { vi.clearAllMocks(); }); const renderUseDialog = () => { let api: any; const TestComponent = () => { const [dialogApi, contextHolder] = useDialog(); api = dialogApi; return contextHolder as any; }; render(); return api; }; it('should call destroy (and thus closeFunc) when close is called without preventClose', async () => { const api = renderUseDialog(); const flowContext = createMockFlowContext(); const dialog = api.open({}, flowContext); await dialog.close(); expect(mockCloseFunc).toHaveBeenCalled(); }); it('should not call destroy (and thus closeFunc) when close is called with preventClose=true', async () => { const api = renderUseDialog(); const flowContext = createMockFlowContext(); const dialog = api.open({ preventClose: true }, flowContext); await dialog.close(); expect(mockCloseFunc).not.toHaveBeenCalled(); }); it('should call destroy (and thus closeFunc) when close is called with preventClose=true but force=true', async () => { const api = renderUseDialog(); const flowContext = createMockFlowContext(); const dialog = api.open({ preventClose: true }, flowContext); await dialog.close(undefined, true); expect(mockCloseFunc).toHaveBeenCalled(); }); it('should delegate to navigation.back when triggerByRouter is true', async () => { const api = renderUseDialog(); const flowContext = createMockFlowContext(); const backMock = vi.fn(); const dialog = api.open( { triggerByRouter: true, inputArgs: { navigation: { back: backMock, }, }, }, flowContext, ); await dialog.close(); expect(backMock).toHaveBeenCalled(); // Should not call destroy directly, let router handle it expect(mockCloseFunc).not.toHaveBeenCalled(); }); it('should emit view activated event on opener engine', async () => { const api = renderUseDialog(); const flowContext = createMockFlowContext(); const emitSpy = flowContext.engine.emitter.emit; const dialog = api.open({ inputArgs: { viewUid: 'child-view' } }, flowContext); await dialog.close(); expect(emitSpy).toHaveBeenCalledWith('view:activated', expect.anything()); }); it('should emit view events on immediate opener engine (previousEngine) when present', async () => { const api = renderUseDialog(); const flowContext = createMockFlowContext(); const rootEmitSpy = flowContext.engine.emitter.emit; const openerEmitSpy = vi.fn(); (flowContext.engine as any).nextEngine = { emitter: { emit: openerEmitSpy }, __NOCOBASE_ENGINE_SCOPE__: 'view' }; const dialog = api.open({ inputArgs: { viewUid: 'child-view' } }, flowContext); await dialog.close(); expect(openerEmitSpy).toHaveBeenCalledWith('view:activated', expect.anything()); expect(rootEmitSpy).not.toHaveBeenCalledWith('view:activated', expect.anything()); }); });