UNPKG

@furystack/shades

Version:

A lightweight UI framework for FuryStack with JSX support

152 lines 8.18 kB
import { createInjector } from '@furystack/inject'; import { deserializeQueryString, serializeToQueryString, serializeValue } from '@furystack/rest'; import { usingAsync } from '@furystack/utils'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { LocationService, useCustomSearchStateSerializer } from './location-service.js'; describe('LocationService', () => { beforeEach(() => { document.body.innerHTML = '<div id="root"></div>'; }); afterEach(() => { document.body.innerHTML = ''; }); it('Shuld be constructed', async () => { await usingAsync(createInjector(), async (i) => { const s = i.get(LocationService); expect(s).toBeDefined(); expect(typeof s.navigate).toBe('function'); }); }); it('Shuld update state on events', async () => { await usingAsync(createInjector(), async (i) => { const onLocaionChanged = vi.fn(); const s = i.get(LocationService); s.onLocationPathChanged.subscribe(onLocaionChanged); expect(onLocaionChanged).toBeCalledTimes(0); history.pushState(null, '', '/loc1'); expect(onLocaionChanged).toBeCalledTimes(1); history.replaceState(null, '', '/loc2'); expect(onLocaionChanged).toBeCalledTimes(2); // TODO: Figure out testing hashchange and popstate subscriptions // window.dispatchEvent(new HashChangeEvent('hashchange', { newURL: '/loc3' })) // expect(onLocaionChanged).toBeCalledTimes(3) // window.dispatchEvent(new PopStateEvent('popstate', {})) // expect(onLocaionChanged).toBeCalledTimes(4) }); }); it('Should update location path when navigate is called', async () => { await usingAsync(createInjector(), async (i) => { const onLocationChanged = vi.fn(); const s = i.get(LocationService); s.onLocationPathChanged.subscribe(onLocationChanged); s.navigate('/dashboard'); expect(s.onLocationPathChanged.getValue()).toBe('/dashboard'); expect(onLocationChanged).toHaveBeenCalledWith('/dashboard'); }); }); describe('replace', () => { it('Should update the observable path without pushing a new history entry', async () => { await usingAsync(createInjector(), async (i) => { const s = i.get(LocationService); const lengthBefore = history.length; s.replace('/replaced'); expect(s.onLocationPathChanged.getValue()).toBe('/replaced'); expect(history.length).toBe(lengthBefore); }); }); it('Should call history.replaceState rather than pushState', async () => { await usingAsync(createInjector(), async (i) => { const s = i.get(LocationService); const pushSpy = vi.spyOn(history, 'pushState'); const replaceSpy = vi.spyOn(history, 'replaceState'); s.replace('/replaced-2'); expect(replaceSpy).toHaveBeenCalledTimes(1); expect(replaceSpy).toHaveBeenCalledWith(null, '', '/replaced-2'); expect(pushSpy).not.toHaveBeenCalled(); pushSpy.mockRestore(); replaceSpy.mockRestore(); }); }); it('Should notify path subscribers after replace', async () => { await usingAsync(createInjector(), async (i) => { const s = i.get(LocationService); const onLocationChanged = vi.fn(); s.onLocationPathChanged.subscribe(onLocationChanged); s.replace('/notify'); expect(onLocationChanged).toHaveBeenCalledWith('/notify'); }); }); }); describe('useSearchParam', () => { it('Should create observables lazily', async () => { await usingAsync(createInjector(), async (i) => { const service = i.get(LocationService); const observables = service.searchParamObservables; const testSearchParam = service.useSearchParam('test', null); expect(observables.size).toBe(1); const testSearchParam2 = service.useSearchParam('test', null); expect(observables.size).toBe(1); expect(testSearchParam).toBe(testSearchParam2); const testSearchParam3 = service.useSearchParam('test2', undefined); expect(observables.size).toBe(2); expect(testSearchParam3).not.toBe(testSearchParam2); }); }); it('Should return the default value, if not present in the query string', async () => { await usingAsync(createInjector(), async (i) => { const service = i.get(LocationService); const testSearchParam = service.useSearchParam('test', { value: 'foo' }); expect(testSearchParam.getValue()).toEqual({ value: 'foo' }); }); }); it('Should return the value from the query string', async () => { await usingAsync(createInjector(), async (i) => { const service = i.get(LocationService); history.pushState(null, '', `/loc1?test=${serializeValue(1)}`); const testSearchParam = service.useSearchParam('test', 123); expect(testSearchParam.getValue()).toBe(1); }); }); it('should update the observable value on push / replace states', async () => { await usingAsync(createInjector(), async (i) => { const service = i.get(LocationService); history.pushState(null, '', `/loc1?test=${serializeValue(1)}`); const testSearchParam = service.useSearchParam('test', 234); expect(testSearchParam.getValue()).toBe(1); history.replaceState(null, '', `/loc1?test=${serializeValue('2')}`); expect(testSearchParam.getValue()).toBe('2'); }); }); it('Should update the URL based on search value change', async () => { await usingAsync(createInjector(), async (i) => { const service = i.get(LocationService); history.pushState(null, '', `/loc1?test=${serializeValue('2')}`); const testSearchParam = service.useSearchParam('test', ''); testSearchParam.setValue('2'); expect(location.search).toBe('?test=IjIi'); }); }); it('Should throw when called after LocationService has been resolved', async () => { await usingAsync(createInjector(), async (i) => { const customSerializer = vi.fn((value) => serializeToQueryString(value)); const customDeserializer = vi.fn((value) => deserializeQueryString(value)); // Eagerly resolve once so the service patches history / adds listeners. i.get(LocationService); expect(() => useCustomSearchStateSerializer(i, customSerializer, customDeserializer)).toThrow(/before LocationService is resolved/); }); }); it('Should use custom serializer and deserializer', async () => { await usingAsync(createInjector(), async (i) => { const customSerializer = vi.fn((value) => serializeToQueryString(value)); const customDeserializer = vi.fn((value) => deserializeQueryString(value)); useCustomSearchStateSerializer(i, customSerializer, customDeserializer); const locationService = i.get(LocationService); const testSearchParam = locationService.useSearchParam('test', { value: 'foo' }); testSearchParam.setValue({ value: 'bar' }); expect(customSerializer).toBeCalledWith({ test: { value: 'bar' } }); expect(customDeserializer).toBeCalledWith('?test=eyJ2YWx1ZSI6ImJhciJ9'); }); }); }); }); //# sourceMappingURL=location-service.spec.js.map