Skip to content

Commit db54929

Browse files
authored
feat(Redirector): Add constructor overload for easy use of options (#126)
1 parent 1338a83 commit db54929

File tree

2 files changed

+203
-3
lines changed

2 files changed

+203
-3
lines changed

src/lib/Redirector.svelte.test.ts

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,191 @@ ROUTING_UNIVERSES.forEach((universe) => {
241241
expect(navigateSpy).toHaveBeenCalledTimes(1);
242242
expect(ruPath()).toBe('/punch-line');
243243
});
244+
245+
describe("Constructor Signatures", () => {
246+
test("Should work with explicit hash constructor (hash, options).", () => {
247+
// Arrange.
248+
location.navigate('/explicit-hash', { hash: universe.hash });
249+
navigateSpy.mockClear();
250+
251+
// Act.
252+
const redirector = new Redirector(universe.hash, { replace: false });
253+
redirector.redirections.push({
254+
pattern: '/explicit-hash',
255+
href: '/redirected-explicit'
256+
});
257+
flushSync();
258+
259+
// Assert.
260+
expect(navigateSpy).toHaveBeenCalledWith('/redirected-explicit', expect.objectContaining({
261+
hash: resolvedHash,
262+
replace: false
263+
}));
264+
expect(ruPath()).toBe('/redirected-explicit');
265+
});
266+
267+
test("Should resolve hash correctly when using explicit constructor.", () => {
268+
// This test works for all universes
269+
// Arrange.
270+
location.navigate('/hash-resolution-test', { hash: universe.hash });
271+
navigateSpy.mockClear();
272+
273+
// Act.
274+
const redirector = new Redirector(universe.hash, { replace: true });
275+
redirector.redirections.push({
276+
pattern: '/hash-resolution-test',
277+
href: '/hash-resolved'
278+
});
279+
flushSync();
280+
281+
// Assert.
282+
expect(navigateSpy).toHaveBeenCalledWith('/hash-resolved', expect.objectContaining({
283+
hash: resolvedHash // Should match the explicitly provided hash
284+
}));
285+
expect(ruPath()).toBe('/hash-resolved');
286+
});
287+
});
288+
289+
describe("Options-Only Constructor", () => {
290+
// Only test the options-only constructor for universes that have a defaultHash
291+
// The options-only constructor is designed to work with the "default routing universe"
292+
293+
if (universe.defaultHash !== undefined) {
294+
test("Should work with default hash constructor (options only).", () => {
295+
// Arrange.
296+
location.navigate('/default-hash', { hash: universe.hash });
297+
navigateSpy.mockClear();
298+
299+
// Act.
300+
const redirector = new Redirector({ replace: false });
301+
redirector.redirections.push({
302+
pattern: '/default-hash',
303+
href: '/redirected-default'
304+
});
305+
flushSync();
306+
307+
// Assert.
308+
expect(navigateSpy).toHaveBeenCalledWith('/redirected-default', expect.objectContaining({
309+
hash: resolveHashValue(undefined), // Should use the library's defaultHash
310+
replace: false
311+
}));
312+
});
313+
314+
test("Should work with minimal options-only constructor.", () => {
315+
// Arrange.
316+
location.navigate('/minimal-options', { hash: universe.hash });
317+
navigateSpy.mockClear();
318+
319+
// Act.
320+
const redirector = new Redirector({}); // Empty options object
321+
redirector.redirections.push({
322+
pattern: '/minimal-options',
323+
href: '/redirected-minimal'
324+
});
325+
flushSync();
326+
327+
// Assert.
328+
expect(navigateSpy).toHaveBeenCalledWith('/redirected-minimal', expect.objectContaining({
329+
hash: resolveHashValue(undefined), // Should use the library's defaultHash
330+
replace: true // Should use default replace: true
331+
}));
332+
});
333+
} else {
334+
test("Should skip options-only constructor tests for explicit hash universes.", () => {
335+
// These universes (PR, HR, MHR) use explicit hash values and don't set a defaultHash
336+
// The options-only constructor wouldn't work correctly here since it relies on
337+
// the library's defaultHash matching the universe being tested
338+
expect(universe.text).toMatch(/^(PR|HR|MHR)$/);
339+
});
340+
}
341+
});
342+
});
343+
});
344+
345+
describe("Options-Only Constructor with Matching Library Defaults", () => {
346+
// Test the options-only constructor when the library is properly initialized
347+
// with matching defaultHash values for HR and MHR scenarios
348+
349+
describe("Hash Routing Universe", () => {
350+
let cleanup: () => void;
351+
let navigateSpy: MockInstance<typeof location.navigate>;
352+
353+
beforeAll(() => {
354+
// Initialize library with hash routing as default
355+
cleanup = init({ defaultHash: true });
356+
navigateSpy = vi.spyOn(location, 'navigate');
357+
});
358+
359+
afterAll(() => {
360+
cleanup();
361+
});
362+
363+
afterEach(() => {
364+
location.goTo('/');
365+
vi.clearAllMocks();
366+
});
367+
368+
test("Should work with options-only constructor when library default matches hash routing.", () => {
369+
// Arrange.
370+
location.navigate('/hash-default-test', { hash: true });
371+
navigateSpy.mockClear();
372+
373+
// Act.
374+
const redirector = new Redirector({ replace: false });
375+
redirector.redirections.push({
376+
pattern: '/hash-default-test',
377+
href: '/hash-redirected'
378+
});
379+
flushSync();
380+
381+
// Assert.
382+
expect(navigateSpy).toHaveBeenCalledWith('/hash-redirected', expect.objectContaining({
383+
hash: true, // Should use the library's defaultHash (true)
384+
replace: false
385+
}));
386+
expect(location.hashPaths.single).toBe('/hash-redirected');
387+
});
388+
});
389+
390+
describe("Multi-Hash Routing Universe", () => {
391+
let cleanup: () => void;
392+
let navigateSpy: MockInstance<typeof location.navigate>;
393+
394+
beforeAll(() => {
395+
// Initialize library with multi-hash routing as default
396+
cleanup = init({ defaultHash: 'p1', hashMode: 'multi' });
397+
navigateSpy = vi.spyOn(location, 'navigate');
398+
});
399+
400+
afterAll(() => {
401+
cleanup();
402+
});
403+
404+
afterEach(() => {
405+
location.goTo('/');
406+
vi.clearAllMocks();
407+
});
408+
409+
test("Should work with options-only constructor when library default matches multi-hash routing.", () => {
410+
// Arrange.
411+
location.navigate('/multi-hash-default-test', { hash: 'p1' });
412+
navigateSpy.mockClear();
413+
414+
// Act.
415+
const redirector = new Redirector({ replace: false });
416+
redirector.redirections.push({
417+
pattern: '/multi-hash-default-test',
418+
href: '/multi-hash-redirected'
419+
});
420+
flushSync();
421+
422+
// Assert.
423+
expect(navigateSpy).toHaveBeenCalledWith('/multi-hash-redirected', expect.objectContaining({
424+
hash: 'p1', // Should use the library's defaultHash ('p1')
425+
replace: false
426+
}));
427+
expect(location.hashPaths.p1).toBe('/multi-hash-redirected');
428+
});
244429
});
245430
});
246431

src/lib/Redirector.svelte.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,26 @@ export class Redirector {
9595
#routePatterns = $derived.by(() => this.redirections.map((url) => this.#routeHelper.parseRoutePattern(url)));
9696
/**
9797
* Initializes a new instance of this class.
98-
* @param hash Resolved hash value that will be used for route testing and navigation if no navigation-specific
99-
* hash value is provided via the redirection options.
98+
* @param hash Hash value that will be used for route testing and navigation if no navigation-specific hash value
99+
* is provided via the redirection options.
100100
* @param options Redirector options.
101101
*/
102-
constructor(hash?: Hash | undefined, options?: RedirectorOptions) {
102+
constructor(hash?: Hash | undefined, options?: RedirectorOptions);
103+
/**
104+
* Initializes a new instance of this class that monitors the default routing universe specified during library
105+
* initialization.
106+
* @param options Redirector options.
107+
*/
108+
constructor(options: RedirectorOptions);
109+
constructor(hashOrOptions?: Hash | RedirectorOptions, optionsArg?: RedirectorOptions) {
110+
let hash: Hash | undefined;
111+
let options: RedirectorOptions | undefined;
112+
if (typeof hashOrOptions === 'object') {
113+
options = hashOrOptions;
114+
} else {
115+
hash = hashOrOptions;
116+
options = optionsArg;
117+
}
103118
this.#options = { ...defaultRedirectorOptions, ...options };
104119
this.#hash = resolveHashValue(hash);
105120
this.redirections = $state<RedirectedRouteInfo[]>([]);

0 commit comments

Comments
 (0)