Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 32 additions & 36 deletions src/lib/kernel/initCore.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { initCore } from './initCore.js';
import { SvelteURL } from "svelte/reactivity";
import { location } from "./Location.js";
import { defaultTraceOptions, traceOptions } from "./trace.svelte.js";
import { defaultRoutingOptions, routingOptions } from "./options.js";
import { defaultRoutingOptions, resetRoutingOptions, routingOptions } from "./options.js";
import { logger } from "./Logger.js";

const initialUrl = 'http://example.com/';
Expand All @@ -19,13 +19,15 @@ const locationMock: Location = {
on: vi.fn(),
go: vi.fn(),
navigate: vi.fn(),
get path() { return this.url.pathname; },
};

describe('initCore', () => {
let cleanup: (() => void) | undefined;
afterEach(() => {
vi.resetAllMocks();
cleanup?.();
resetRoutingOptions();
});
test("Should initialize with all the expected default values.", () => {
// Act.
Expand Down Expand Up @@ -53,20 +55,10 @@ describe('initCore', () => {
error: () => { }
};

// Capture initial state
const initialLoggerIsOffLogger = logger !== globalThis.console;
const initialRoutingOptions = {
hashMode: routingOptions.hashMode,
defaultHash: routingOptions.defaultHash
};
const initialTraceOptions = {
routerHierarchy: traceOptions.routerHierarchy
};

// Act - Initialize with custom options
// Act - Initialize with custom options (use valid combo)
cleanup = initCore(locationMock, {
hashMode: 'multi',
defaultHash: true,
defaultHash: 'customHash',
logger: customLogger,
trace: {
routerHierarchy: true
Expand All @@ -76,19 +68,19 @@ describe('initCore', () => {
// Assert - Check that options were applied
expect(logger).toBe(customLogger);
expect(routingOptions.hashMode).toBe('multi');
expect(routingOptions.defaultHash).toBe(true);
expect(routingOptions.defaultHash).toBe('customHash');
expect(traceOptions.routerHierarchy).toBe(true);
expect(location).toBeDefined();

// Act - Cleanup
cleanup();
cleanup = undefined;

// Assert - Check that everything was rolled back
expect(logger !== globalThis.console).toBe(initialLoggerIsOffLogger); // Back to offLogger
expect(routingOptions.hashMode).toBe(initialRoutingOptions.hashMode);
expect(routingOptions.defaultHash).toBe(initialRoutingOptions.defaultHash);
expect(traceOptions.routerHierarchy).toBe(initialTraceOptions.routerHierarchy);
// Assert - Check that everything was rolled back to library defaults
expect(logger).not.toBe(customLogger); // Should revert to offLogger
expect(routingOptions.hashMode).toBe(defaultRoutingOptions.hashMode);
expect(routingOptions.defaultHash).toBe(defaultRoutingOptions.defaultHash);
expect(traceOptions.routerHierarchy).toBe(defaultTraceOptions.routerHierarchy);
expect(location).toBeNull();
});
test("Should throw an error when called a second time without proper prior cleanup.", () => {
Expand All @@ -103,53 +95,57 @@ describe('initCore', () => {
});
describe('cleanup', () => {
test("Should rollback everything to defaults.", async () => {
// Arrange.
const uninitializedLogger = logger;
// Arrange - Initialize with custom options
cleanup = initCore(locationMock, {
hashMode: 'multi',
defaultHash: true,
defaultHash: 'abc',
trace: {
routerHierarchy: !defaultTraceOptions.routerHierarchy
}
});
// Verify options were applied
// Verify options were applied (no type conversion occurs)
expect(routingOptions.hashMode).toBe('multi');
expect(routingOptions.defaultHash).toBe(true);
expect(logger).not.toBe(uninitializedLogger);
expect(routingOptions.defaultHash).toBe('abc');
expect(logger).toBe(globalThis.console); // Default logger when none specified
expect(traceOptions.routerHierarchy).toBe(!defaultTraceOptions.routerHierarchy);

// Act - Cleanup
cleanup();
cleanup = undefined;

// Assert - Check that routing options were reset to defaults
expect(routingOptions.hashMode).toBe('single');
expect(routingOptions.defaultHash).toBe(false);
expect(logger).toBe(uninitializedLogger);
// Assert - Check that all options were reset to library defaults
expect(routingOptions.hashMode).toBe(defaultRoutingOptions.hashMode);
expect(routingOptions.defaultHash).toBe(defaultRoutingOptions.defaultHash);
expect(logger).not.toBe(globalThis.console); // Reverts to offLogger (uninitialized state)
expect(traceOptions).toEqual(defaultTraceOptions);
});
test("Should handle multiple init/cleanup cycles properly.", async () => {
// Arrange.
const uninitializedLogger = logger;
// Capture initial logger state for comparison
const initialLogger = logger;

// First cycle
cleanup = initCore(locationMock, {
logger: { debug: () => { }, log: () => { }, warn: () => { }, error: () => { } }
});
expect(logger).not.toBe(uninitializedLogger);
expect(logger).not.toBe(initialLogger);
expect(location).toBeDefined();

cleanup();
cleanup = undefined;
expect(logger).toBe(uninitializedLogger);
expect(logger).toBe(initialLogger); // Back to initial state
expect(location).toBeNull();

// Second cycle
cleanup = initCore(locationMock, { hashMode: 'multi' });
expect(routingOptions.hashMode).toBe('multi');
expect(location).toBeDefined();

// Act.
// Act - Final cleanup
cleanup();
cleanup = undefined;

// Assert.
expect(routingOptions.hashMode).toBe('single');
// Assert - Should revert to library defaults
expect(routingOptions.hashMode).toBe(defaultRoutingOptions.hashMode);
expect(location).toBeNull();
});
});
Expand Down
132 changes: 127 additions & 5 deletions src/lib/kernel/options.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, test } from "vitest";
import { resetRoutingOptions, routingOptions } from "./options.js";
import { describe, expect, test, beforeEach } from "vitest";
import { resetRoutingOptions, routingOptions, setRoutingOptions } from "./options.js";

describe("options", () => {
test("Should have correct default value for hashMode option.", () => {
Expand Down Expand Up @@ -47,10 +47,132 @@ describe("options", () => {
expect(typeof routingOptions.defaultHash).toBe('boolean');
});

describe('setRoutingOptions', () => {
beforeEach(() => {
// Reset to defaults before each test
resetRoutingOptions();
});

test("Should merge options with current values when partial options provided.", () => {
// Arrange - Set initial non-default values
routingOptions.hashMode = 'multi';
routingOptions.defaultHash = 'customHash';

// Act - Set only one option
setRoutingOptions({ disallowPathRouting: true });

// Assert - Only specified option changed, others preserved
expect(routingOptions.hashMode).toBe('multi');
expect(routingOptions.defaultHash).toBe('customHash');
expect(routingOptions.disallowPathRouting).toBe(true);
expect(routingOptions.disallowHashRouting).toBe(false);
});

test("Should set all options when full configuration provided.", () => {
// Arrange & Act
setRoutingOptions({
hashMode: 'multi',
defaultHash: 'namedHash',
disallowPathRouting: true,
disallowHashRouting: true,
disallowMultiHashRouting: false
});

// Assert
expect(routingOptions.hashMode).toBe('multi');
expect(routingOptions.defaultHash).toBe('namedHash');
expect(routingOptions.disallowPathRouting).toBe(true);
expect(routingOptions.disallowHashRouting).toBe(true);
expect(routingOptions.disallowMultiHashRouting).toBe(false);
});

test("Should do nothing when called with undefined options.", () => {
// Arrange - Set initial values
const original = structuredClone(routingOptions);

// Act
setRoutingOptions(undefined);

// Assert - No changes
expect(routingOptions).deep.equal(original);
});

test("Should do nothing when called with empty options.", () => {
// Arrange - Set initial values
const original = structuredClone(routingOptions);

// Act
setRoutingOptions({});

// Assert - No changes
expect(routingOptions).deep.equal(original);
});

describe('Runtime validation', () => {
test("Should throw error when hashMode is 'single' and defaultHash is a string.", () => {
// Arrange & Act & Assert
expect(() => {
setRoutingOptions({
hashMode: 'single',
defaultHash: 'namedHash'
});
}).toThrow("Using a named hash path as the default path can only be done when 'hashMode' is set to 'multi'.");
});

test("Should throw error when hashMode is 'multi' and defaultHash is true.", () => {
// Arrange & Act & Assert
expect(() => {
setRoutingOptions({
hashMode: 'multi',
defaultHash: true
});
}).toThrow("Using classic hash routing as default can only be done when 'hashMode' is set to 'single'.");
});

test("Should throw error when existing hashMode is 'single' and setting defaultHash to string.", () => {
// Arrange
routingOptions.hashMode = 'single';

// Act & Assert
expect(() => {
setRoutingOptions({ defaultHash: 'namedHash' });
}).toThrow("Using a named hash path as the default path can only be done when 'hashMode' is set to 'multi'.");
});

test("Should throw error when existing defaultHash is true and setting hashMode to 'multi'.", () => {
// Arrange
routingOptions.defaultHash = true;

// Act & Assert
expect(() => {
setRoutingOptions({ hashMode: 'multi' });
}).toThrow("Using classic hash routing as default can only be done when 'hashMode' is set to 'single'.");
});

test.each([
{ hashMode: 'single' as const, defaultHash: false, scenario: 'single hash mode with defaultHash false' },
{ hashMode: 'single' as const, defaultHash: true, scenario: 'single hash mode with defaultHash true' },
{ hashMode: 'multi' as const, defaultHash: false, scenario: 'multi hash mode with defaultHash false' },
{ hashMode: 'multi' as const, defaultHash: 'namedHash', scenario: 'multi hash mode with named hash' }
])("Should allow valid combination: $scenario .", ({ hashMode, defaultHash }) => {
// Arrange & Act & Assert
expect(() => {
setRoutingOptions({ hashMode, defaultHash });
}).not.toThrow();

expect(routingOptions.hashMode).toBe(hashMode);
expect(routingOptions.defaultHash).toBe(defaultHash);
});
});
});

describe('resetRoutingOptions', () => {
test("Should reset all options to defaults when resetRoutingOptions is called.", () => {
// Arrange.
const original = structuredClone(routingOptions);
// Arrange - First reset to ensure we start from defaults, then capture the baseline
resetRoutingOptions();
const expectedDefaults = structuredClone(routingOptions);

// Modify all options to non-default values
routingOptions.hashMode = 'multi';
routingOptions.defaultHash = true;
routingOptions.disallowPathRouting = true;
Expand All @@ -61,7 +183,7 @@ describe("options", () => {
resetRoutingOptions();

// Assert.
expect(routingOptions).deep.equal(original);
expect(routingOptions).deep.equal(expectedDefaults);
});
});
});
6 changes: 6 additions & 0 deletions src/lib/kernel/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ export function setRoutingOptions(options?: Partial<ExtendedRoutingOptions>): vo
routingOptions.disallowPathRouting = options?.disallowPathRouting ?? routingOptions.disallowPathRouting;
routingOptions.disallowHashRouting = options?.disallowHashRouting ?? routingOptions.disallowHashRouting;
routingOptions.disallowMultiHashRouting = options?.disallowMultiHashRouting ?? routingOptions.disallowMultiHashRouting;
if (routingOptions.hashMode === 'single' && typeof routingOptions.defaultHash === 'string') {
throw new Error("Using a named hash path as the default path can only be done when 'hashMode' is set to 'multi'.");
}
else if (routingOptions.hashMode === 'multi' && routingOptions.defaultHash === true) {
throw new Error("Using classic hash routing as default can only be done when 'hashMode' is set to 'single'.");
}
}

/**
Expand Down