Task model displays now resolve task-scoped workflow settings consistently with execution. - Add a shared workflow settings overlay helper for stored workflow values and declaration defaults. - Expose task effective settings through the dashboard API and consume them in TaskDetail model surfaces. - Cover core overlay behavior, route output, and Workflow tab model rendering with regression tests. - Document workflow model lane precedence and add a published package changeset. Files changed: .changeset/fn-7123-workflow-tab-project-model.md | 7 ++ docs/settings-reference.md | 14 ++- .../__tests__/effective-settings-overlay.test.ts | 70 ++++++++++++++ packages/core/src/effective-settings-overlay.ts | 27 ++++++ packages/core/src/index.ts | 4 + packages/dashboard/app/api/legacy.ts | 5 + .../dashboard/app/components/TaskDetailModal.tsx | 13 ++- ...skDetailModal.models-progress-workflow.test.tsx | 46 +++++++++ .../__tests__/TaskDetailModal.test-helpers.ts | 1 + .../__tests__/WorkflowResultsTab.test.tsx | 33 +++++++ packages/dashboard/app/test/mockApi.ts | 9 ++ .../task-effective-settings-route.test.ts | 107 +++++++++++++++++++++ .../src/routes/register-task-workflow-routes.ts | 21 ++++ packages/engine/src/effective-settings.ts | 25 ++--- 14 files changed, 355 insertions(+), 27 deletions(-) Fusion-Task-Id: FN-7123 Fusion-Task-Lineage: 5fa9d338-c886-44aa-bdba-0d361b3602d5 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
78 lines
3.0 KiB
TypeScript
78 lines
3.0 KiB
TypeScript
/**
|
|
* Canonical dashboard app API mock helper.
|
|
*
|
|
* Add new `app/api.ts` or `app/api/legacy.ts` exports here first before
|
|
* introducing ad-hoc per-test `vi.mock("../../api", …)` export lists.
|
|
*
|
|
* Behavior contract:
|
|
* - preserve real exports by default
|
|
* - apply canonical/common test mocks
|
|
* - allow per-suite overrides
|
|
* - auto-synthesize stable fallback fns for missing callable exports
|
|
*/
|
|
import { vi, type Mock } from "vitest";
|
|
|
|
type AnyFn = Mock;
|
|
type AnyModule = Record<string, unknown>;
|
|
|
|
const fallbackFns = new Map<string, AnyFn>();
|
|
|
|
function getFallback(name: string): AnyFn {
|
|
if (!fallbackFns.has(name)) {
|
|
fallbackFns.set(name, vi.fn(async () => undefined));
|
|
}
|
|
return fallbackFns.get(name)!;
|
|
}
|
|
|
|
export const dashboardApiMocks: Record<string, AnyFn> = {
|
|
fetchTasks: vi.fn(async () => []),
|
|
fetchSettings: vi.fn(async () => ({})),
|
|
fetchTaskEffectiveSettings: vi.fn().mockRejectedValue(new Error("fetchTaskEffectiveSettings: use fetchSettings mock")),
|
|
updateSettings: vi.fn(async () => ({})),
|
|
fetchGlobalSettings: vi.fn(async () => ({})),
|
|
fetchAuthStatus: vi.fn(async () => ({ providers: [] })),
|
|
fetchModels: vi.fn(async () => ({ models: [], favoriteProviders: [], favoriteModels: [] })),
|
|
fetchTaskDetail: vi.fn(),
|
|
fetchTaskReview: vi.fn(),
|
|
fetchUnreadCount: vi.fn(async () => ({ unreadCount: 0 })),
|
|
};
|
|
|
|
export async function createDashboardApiMock(
|
|
importActual: () => Promise<AnyModule>,
|
|
overrides: Record<string, AnyFn> = {},
|
|
): Promise<AnyModule> {
|
|
const actual = await importActual();
|
|
const mocked: AnyModule = { ...actual, ...dashboardApiMocks, ...overrides };
|
|
|
|
return new Proxy(mocked, {
|
|
get(target, prop, receiver) {
|
|
if (typeof prop !== "string") return Reflect.get(target, prop, receiver);
|
|
if (Reflect.has(target, prop)) return Reflect.get(target, prop, receiver);
|
|
|
|
if (["then", "catch", "finally"].includes(prop)) {
|
|
return undefined;
|
|
}
|
|
const actualValue = (actual as AnyModule)[prop];
|
|
if (typeof actualValue === "function" || actualValue === undefined) {
|
|
const fn = getFallback(prop);
|
|
(target as AnyModule)[prop] = fn;
|
|
return fn;
|
|
}
|
|
return actualValue;
|
|
},
|
|
});
|
|
}
|
|
|
|
export function resetDashboardApiMockState(): void {
|
|
Object.values(dashboardApiMocks).forEach((fn) => fn.mockReset());
|
|
dashboardApiMocks.fetchTasks.mockResolvedValue([]);
|
|
dashboardApiMocks.fetchSettings.mockResolvedValue({});
|
|
dashboardApiMocks.fetchTaskEffectiveSettings.mockRejectedValue(new Error("fetchTaskEffectiveSettings: use fetchSettings mock"));
|
|
dashboardApiMocks.updateSettings.mockResolvedValue({});
|
|
dashboardApiMocks.fetchGlobalSettings.mockResolvedValue({});
|
|
dashboardApiMocks.fetchAuthStatus.mockResolvedValue({ providers: [] });
|
|
dashboardApiMocks.fetchModels.mockResolvedValue({ models: [], favoriteProviders: [], favoriteModels: [] });
|
|
dashboardApiMocks.fetchUnreadCount.mockResolvedValue({ unreadCount: 0 });
|
|
for (const fn of fallbackFns.values()) fn.mockReset();
|
|
}
|