mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-12 00:17:02 +02:00
Add per-workspace layout configuration with smart auto-balancing: **Core Changes:** - Add LayoutConfig interface to Workspace type with insertionMode, splitPercentage, insertionPosition - Create layout-utils.ts with smart direction algorithm that auto-balances horizontal/vertical splits - Update addWindow() to use workspace layoutConfig instead of hardcoded values - Migrate state from v7→v8, adding layoutConfig to all workspaces with smart defaults **Smart Direction Algorithm:** - Analyzes layout tree to count horizontal vs vertical splits - Automatically balances by favoring the less-common direction - Defaults to horizontal (row) for first split - Provides foundation for "Balanced (auto)" insertion mode **Testing:** - Add 30 comprehensive tests for layout-utils.ts (tree analysis, smart direction, window insertion) - Add 30 tests for logic.ts addWindow() with different layout configs (row/column/smart modes) - Update migration tests to verify v6→v7→v8 and v7→v8 paths **Migration:** - v7→v8 adds layoutConfig with defaults: smart mode, 50% split, second position - All existing workspaces automatically get smart auto-balancing behavior Implements orthogonal design: layout behavior = workspace settings (not command flags)
274 lines
7.4 KiB
TypeScript
274 lines
7.4 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { migrateState, validateState, CURRENT_VERSION } from "./migrations";
|
|
|
|
describe("migrations", () => {
|
|
describe("v6 to v8 migration (v6→v7→v8)", () => {
|
|
it("should convert numeric labels to number field and add layoutConfig", () => {
|
|
const oldState = {
|
|
__version: 6,
|
|
windows: {},
|
|
activeWorkspaceId: "ws1",
|
|
workspaces: {
|
|
ws1: {
|
|
id: "ws1",
|
|
label: "1",
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
ws2: {
|
|
id: "ws2",
|
|
label: "2",
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
const migrated = migrateState(oldState);
|
|
|
|
// Should migrate to v8
|
|
expect(migrated.__version).toBe(CURRENT_VERSION);
|
|
|
|
// v6→v7: numeric labels converted to number
|
|
expect(migrated.workspaces.ws1.number).toBe(1);
|
|
expect(migrated.workspaces.ws1.label).toBeUndefined();
|
|
expect(migrated.workspaces.ws2.number).toBe(2);
|
|
expect(migrated.workspaces.ws2.label).toBeUndefined();
|
|
|
|
// v7→v8: layoutConfig added
|
|
expect(migrated.workspaces.ws1.layoutConfig).toEqual({
|
|
insertionMode: "smart",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
autoPreset: undefined,
|
|
});
|
|
expect(migrated.workspaces.ws2.layoutConfig).toEqual({
|
|
insertionMode: "smart",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
autoPreset: undefined,
|
|
});
|
|
});
|
|
|
|
it("should convert non-numeric labels to number with label and add layoutConfig", () => {
|
|
const oldState = {
|
|
__version: 6,
|
|
windows: {},
|
|
activeWorkspaceId: "ws1",
|
|
workspaces: {
|
|
ws1: {
|
|
id: "ws1",
|
|
label: "Main",
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
ws2: {
|
|
id: "ws2",
|
|
label: "Development",
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
const migrated = migrateState(oldState);
|
|
|
|
expect(migrated.__version).toBe(CURRENT_VERSION);
|
|
|
|
// v6→v7: non-numeric labels preserved
|
|
expect(migrated.workspaces.ws1.number).toBe(1);
|
|
expect(migrated.workspaces.ws1.label).toBe("Main");
|
|
expect(migrated.workspaces.ws2.number).toBe(2);
|
|
expect(migrated.workspaces.ws2.label).toBe("Development");
|
|
|
|
// v7→v8: layoutConfig added
|
|
expect(migrated.workspaces.ws1.layoutConfig).toBeDefined();
|
|
expect(migrated.workspaces.ws2.layoutConfig).toBeDefined();
|
|
});
|
|
|
|
it("should handle mixed numeric and text labels and add layoutConfig", () => {
|
|
const oldState = {
|
|
__version: 6,
|
|
windows: {},
|
|
activeWorkspaceId: "ws1",
|
|
workspaces: {
|
|
ws1: {
|
|
id: "ws1",
|
|
label: "1",
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
ws2: {
|
|
id: "ws2",
|
|
label: "Main",
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
ws3: {
|
|
id: "ws3",
|
|
label: "3",
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
const migrated = migrateState(oldState);
|
|
|
|
expect(migrated.__version).toBe(CURRENT_VERSION);
|
|
|
|
// v6→v7: mixed labels handled correctly
|
|
expect(migrated.workspaces.ws1.number).toBe(1);
|
|
expect(migrated.workspaces.ws1.label).toBeUndefined();
|
|
expect(migrated.workspaces.ws2.number).toBe(2);
|
|
expect(migrated.workspaces.ws2.label).toBe("Main");
|
|
expect(migrated.workspaces.ws3.number).toBe(3);
|
|
expect(migrated.workspaces.ws3.label).toBeUndefined();
|
|
|
|
// v7→v8: layoutConfig added to all workspaces
|
|
expect(migrated.workspaces.ws1.layoutConfig).toBeDefined();
|
|
expect(migrated.workspaces.ws2.layoutConfig).toBeDefined();
|
|
expect(migrated.workspaces.ws3.layoutConfig).toBeDefined();
|
|
});
|
|
|
|
it("should validate migrated state", () => {
|
|
const oldState = {
|
|
__version: 6,
|
|
windows: {},
|
|
activeWorkspaceId: "ws1",
|
|
workspaces: {
|
|
ws1: {
|
|
id: "ws1",
|
|
label: "1",
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
const migrated = migrateState(oldState);
|
|
expect(validateState(migrated)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("v7 to v8 migration", () => {
|
|
it("should add layoutConfig to existing workspaces", () => {
|
|
const v7State = {
|
|
__version: 7,
|
|
windows: {
|
|
w1: { id: "w1", appId: "profile", props: {} },
|
|
w2: { id: "w2", appId: "nip", props: {} },
|
|
},
|
|
activeWorkspaceId: "ws1",
|
|
workspaces: {
|
|
ws1: {
|
|
id: "ws1",
|
|
number: 1,
|
|
label: undefined,
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
ws2: {
|
|
id: "ws2",
|
|
number: 2,
|
|
label: "Development",
|
|
layout: { direction: "row", first: "w1", second: "w2", splitPercentage: 50 },
|
|
windowIds: ["w1", "w2"],
|
|
},
|
|
},
|
|
};
|
|
|
|
const migrated = migrateState(v7State);
|
|
|
|
expect(migrated.__version).toBe(8);
|
|
expect(migrated.workspaces.ws1.layoutConfig).toEqual({
|
|
insertionMode: "smart",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
autoPreset: undefined,
|
|
});
|
|
expect(migrated.workspaces.ws2.layoutConfig).toEqual({
|
|
insertionMode: "smart",
|
|
splitPercentage: 50,
|
|
insertionPosition: "second",
|
|
autoPreset: undefined,
|
|
});
|
|
|
|
// Existing fields should be preserved
|
|
expect(migrated.workspaces.ws2.label).toBe("Development");
|
|
expect(migrated.workspaces.ws2.layout).toEqual({
|
|
direction: "row",
|
|
first: "w1",
|
|
second: "w2",
|
|
splitPercentage: 50,
|
|
});
|
|
});
|
|
|
|
it("should validate v7→v8 migrated state", () => {
|
|
const v7State = {
|
|
__version: 7,
|
|
windows: { w1: { id: "w1", appId: "profile", props: {} } },
|
|
activeWorkspaceId: "ws1",
|
|
workspaces: {
|
|
ws1: {
|
|
id: "ws1",
|
|
number: 1,
|
|
layout: "w1",
|
|
windowIds: ["w1"],
|
|
},
|
|
},
|
|
};
|
|
|
|
const migrated = migrateState(v7State);
|
|
expect(validateState(migrated)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("validateState", () => {
|
|
it("should validate correct state structure", () => {
|
|
const state = {
|
|
__version: CURRENT_VERSION,
|
|
windows: {},
|
|
activeWorkspaceId: "default",
|
|
workspaces: {
|
|
default: {
|
|
id: "default",
|
|
number: 1,
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(validateState(state)).toBe(true);
|
|
});
|
|
|
|
it("should reject state without __version", () => {
|
|
const state = {
|
|
windows: {},
|
|
activeWorkspaceId: "default",
|
|
workspaces: {
|
|
default: {
|
|
id: "default",
|
|
number: 1,
|
|
layout: null,
|
|
windowIds: [],
|
|
},
|
|
},
|
|
};
|
|
|
|
expect(validateState(state)).toBe(false);
|
|
});
|
|
|
|
it("should reject state with missing workspaces", () => {
|
|
const state = {
|
|
__version: CURRENT_VERSION,
|
|
windows: {},
|
|
activeWorkspaceId: "default",
|
|
};
|
|
|
|
expect(validateState(state)).toBe(false);
|
|
});
|
|
});
|
|
});
|