mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-06-08 05:39:52 +02:00
refactor: collapse migrations
This commit is contained in:
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
|
||||
import { migrateState, validateState, CURRENT_VERSION } from "./migrations";
|
||||
|
||||
describe("migrations", () => {
|
||||
describe("v6 to v9 migration (v6→v7→v8→v9)", () => {
|
||||
describe("v6 to v8 migration (v6→v7→v8)", () => {
|
||||
it("should convert numeric labels to number field and add global layoutConfig", () => {
|
||||
const oldState = {
|
||||
__version: 6,
|
||||
@@ -26,7 +26,7 @@ describe("migrations", () => {
|
||||
|
||||
const migrated = migrateState(oldState);
|
||||
|
||||
// Should migrate to v9
|
||||
// Should migrate to v8
|
||||
expect(migrated.__version).toBe(CURRENT_VERSION);
|
||||
|
||||
// v6→v7: numeric labels converted to number
|
||||
@@ -35,14 +35,8 @@ describe("migrations", () => {
|
||||
expect(migrated.workspaces.ws2.number).toBe(2);
|
||||
expect(migrated.workspaces.ws2.label).toBeUndefined();
|
||||
|
||||
// v7→v8→v9: layoutConfig added to each workspace
|
||||
expect(migrated.workspaces.ws1.layoutConfig).toEqual({
|
||||
insertionMode: "smart",
|
||||
splitPercentage: 50,
|
||||
insertionPosition: "second",
|
||||
autoPreset: undefined,
|
||||
});
|
||||
expect(migrated.workspaces.ws2.layoutConfig).toEqual({
|
||||
// v7→v8: global layoutConfig added
|
||||
expect(migrated.layoutConfig).toEqual({
|
||||
insertionMode: "smart",
|
||||
splitPercentage: 50,
|
||||
insertionPosition: "second",
|
||||
@@ -50,7 +44,7 @@ describe("migrations", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should convert non-numeric labels to number with label and add per-workspace layoutConfig", () => {
|
||||
it("should convert non-numeric labels to number with label and add global layoutConfig", () => {
|
||||
const oldState = {
|
||||
__version: 6,
|
||||
windows: {},
|
||||
@@ -81,12 +75,11 @@ describe("migrations", () => {
|
||||
expect(migrated.workspaces.ws2.number).toBe(2);
|
||||
expect(migrated.workspaces.ws2.label).toBe("Development");
|
||||
|
||||
// v7→v8→v9: layoutConfig added to each workspace
|
||||
expect(migrated.workspaces.ws1.layoutConfig).toBeDefined();
|
||||
expect(migrated.workspaces.ws2.layoutConfig).toBeDefined();
|
||||
// v7→v8: global layoutConfig added
|
||||
expect(migrated.layoutConfig).toBeDefined();
|
||||
});
|
||||
|
||||
it("should handle mixed numeric and text labels and add per-workspace layoutConfig", () => {
|
||||
it("should handle mixed numeric and text labels and add global layoutConfig", () => {
|
||||
const oldState = {
|
||||
__version: 6,
|
||||
windows: {},
|
||||
@@ -125,10 +118,8 @@ describe("migrations", () => {
|
||||
expect(migrated.workspaces.ws3.number).toBe(3);
|
||||
expect(migrated.workspaces.ws3.label).toBeUndefined();
|
||||
|
||||
// v7→v8→v9: layoutConfig added to each workspace
|
||||
expect(migrated.workspaces.ws1.layoutConfig).toBeDefined();
|
||||
expect(migrated.workspaces.ws2.layoutConfig).toBeDefined();
|
||||
expect(migrated.workspaces.ws3.layoutConfig).toBeDefined();
|
||||
// v7→v8: global layoutConfig added
|
||||
expect(migrated.layoutConfig).toBeDefined();
|
||||
});
|
||||
|
||||
it("should validate migrated state", () => {
|
||||
@@ -151,104 +142,6 @@ describe("migrations", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("v8 to v9 migration", () => {
|
||||
it("should preserve per-workspace layoutConfig", () => {
|
||||
const v8State = {
|
||||
__version: 8,
|
||||
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: [],
|
||||
layoutConfig: {
|
||||
insertionMode: "smart",
|
||||
splitPercentage: 50,
|
||||
insertionPosition: "second",
|
||||
autoPreset: undefined,
|
||||
},
|
||||
},
|
||||
ws2: {
|
||||
id: "ws2",
|
||||
number: 2,
|
||||
label: "Development",
|
||||
layout: {
|
||||
direction: "row",
|
||||
first: "w1",
|
||||
second: "w2",
|
||||
splitPercentage: 50,
|
||||
},
|
||||
windowIds: ["w1", "w2"],
|
||||
layoutConfig: {
|
||||
insertionMode: "row",
|
||||
splitPercentage: 70,
|
||||
insertionPosition: "first",
|
||||
autoPreset: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const migrated = migrateState(v8State);
|
||||
|
||||
expect(migrated.__version).toBe(9);
|
||||
|
||||
// layoutConfig should remain per-workspace
|
||||
expect(migrated.workspaces.ws1.layoutConfig).toEqual({
|
||||
insertionMode: "smart",
|
||||
splitPercentage: 50,
|
||||
insertionPosition: "second",
|
||||
autoPreset: undefined,
|
||||
});
|
||||
expect(migrated.workspaces.ws2.layoutConfig).toEqual({
|
||||
insertionMode: "row",
|
||||
splitPercentage: 70,
|
||||
insertionPosition: "first",
|
||||
autoPreset: undefined,
|
||||
});
|
||||
|
||||
// Other 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 v8→v9 migrated state", () => {
|
||||
const v8State = {
|
||||
__version: 8,
|
||||
windows: { w1: { id: "w1", appId: "profile", props: {} } },
|
||||
activeWorkspaceId: "ws1",
|
||||
workspaces: {
|
||||
ws1: {
|
||||
id: "ws1",
|
||||
number: 1,
|
||||
layout: "w1",
|
||||
windowIds: ["w1"],
|
||||
layoutConfig: {
|
||||
insertionMode: "smart",
|
||||
splitPercentage: 50,
|
||||
insertionPosition: "second",
|
||||
autoPreset: undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const migrated = migrateState(v8State);
|
||||
expect(validateState(migrated)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("validateState", () => {
|
||||
it("should validate correct state structure", () => {
|
||||
const state = {
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import { GrimoireState } from "@/types/app";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export const CURRENT_VERSION = 9;
|
||||
export const CURRENT_VERSION = 8;
|
||||
|
||||
/**
|
||||
* Migration function type
|
||||
@@ -67,52 +67,18 @@ const migrations: Record<number, MigrationFn> = {
|
||||
workspaces: migratedWorkspaces,
|
||||
};
|
||||
},
|
||||
// Migration from v7 to v8 - adds layoutConfig to workspaces
|
||||
// Migration from v7 to v8 - adds global layoutConfig
|
||||
7: (state: any) => {
|
||||
const migratedWorkspaces: Record<string, any> = {};
|
||||
|
||||
// Add default layoutConfig to each workspace
|
||||
for (const [id, workspace] of Object.entries(state.workspaces || {})) {
|
||||
const ws = workspace as Record<string, any>;
|
||||
migratedWorkspaces[id] = {
|
||||
...ws,
|
||||
layoutConfig: {
|
||||
insertionMode: "smart", // New smart default (auto-balance)
|
||||
splitPercentage: 50, // Matches old 50/50 behavior
|
||||
insertionPosition: "second", // Matches old right/bottom behavior
|
||||
autoPreset: undefined, // No preset by default
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Add global layoutConfig with smart defaults
|
||||
return {
|
||||
...state,
|
||||
__version: 8,
|
||||
workspaces: migratedWorkspaces,
|
||||
};
|
||||
},
|
||||
// Migration from v8 to v9 - preserve per-workspace layoutConfig
|
||||
8: (state: any) => {
|
||||
// Ensure all workspaces have layoutConfig (add default if missing)
|
||||
const migratedWorkspaces: Record<string, any> = {};
|
||||
|
||||
for (const [id, workspace] of Object.entries(state.workspaces || {})) {
|
||||
const ws = workspace as Record<string, any>;
|
||||
migratedWorkspaces[id] = {
|
||||
...ws,
|
||||
layoutConfig: ws.layoutConfig || {
|
||||
insertionMode: "smart",
|
||||
splitPercentage: 50,
|
||||
insertionPosition: "second",
|
||||
autoPreset: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
__version: 9,
|
||||
workspaces: migratedWorkspaces,
|
||||
layoutConfig: {
|
||||
insertionMode: "smart", // Smart auto-balancing
|
||||
splitPercentage: 50, // Equal split
|
||||
insertionPosition: "second", // New windows on right/bottom
|
||||
autoPreset: undefined, // No preset by default
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -133,11 +99,17 @@ export function validateState(state: any): state is GrimoireState {
|
||||
!state.windows ||
|
||||
!state.workspaces ||
|
||||
!state.activeWorkspaceId ||
|
||||
!state.layoutConfig ||
|
||||
typeof state.__version !== "number"
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// layoutConfig must be an object
|
||||
if (typeof state.layoutConfig !== "object") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Windows must be an object
|
||||
if (typeof state.windows !== "object") {
|
||||
return false;
|
||||
@@ -154,16 +126,11 @@ export function validateState(state: any): state is GrimoireState {
|
||||
}
|
||||
|
||||
// All window IDs in workspaces must exist in windows
|
||||
// Each workspace must have layoutConfig
|
||||
for (const workspace of Object.values(state.workspaces)) {
|
||||
const ws = workspace as any;
|
||||
if (!Array.isArray(ws.windowIds)) {
|
||||
return false;
|
||||
}
|
||||
// Verify workspace has layoutConfig
|
||||
if (!ws.layoutConfig || typeof ws.layoutConfig !== "object") {
|
||||
return false;
|
||||
}
|
||||
for (const windowId of ws.windowIds) {
|
||||
if (!state.windows[windowId]) {
|
||||
return false;
|
||||
|
||||
@@ -123,7 +123,15 @@ export function parseTagStructure(tag: TagDefinition): {
|
||||
parts.push(`${current.either.join(" | ")}`);
|
||||
} else {
|
||||
// Replace 'free' with 'text' for better readability
|
||||
const type = current.type === "free" ? "text" : current.type;
|
||||
let type = current.type === "free" ? "text" : current.type;
|
||||
|
||||
// Add examples for specific types
|
||||
if (type === "url") {
|
||||
type = "url (e.g. https://grimoire.rocks)";
|
||||
} else if (type === "relay") {
|
||||
type = "relay (e.g. wss://grimoire.rocks)";
|
||||
}
|
||||
|
||||
parts.push(type);
|
||||
}
|
||||
current = current.next;
|
||||
|
||||
Reference in New Issue
Block a user