mirror of
https://github.com/PeWu/topola-viewer.git
synced 2026-07-22 03:31:51 +00:00
316 lines
9.5 KiB
TypeScript
316 lines
9.5 KiB
TypeScript
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';
|
|
import * as H from 'history';
|
|
import {ChartType} from '../chart';
|
|
import {DataSourceEnum} from '../datasource/data_source';
|
|
import {ChartColors, Ids, PlaceDisplay, Sex} from '../sidepanel/config/config';
|
|
import {
|
|
getArguments,
|
|
getParamFromSearch,
|
|
getStaticUrl,
|
|
getUrlForArgs,
|
|
} from './url_args';
|
|
|
|
describe('url_args', () => {
|
|
const originalEnv = process.env;
|
|
let documentMock: {
|
|
querySelector: jest.Mock;
|
|
};
|
|
|
|
beforeEach(() => {
|
|
process.env = {...originalEnv};
|
|
// Mock document globally
|
|
documentMock = {
|
|
querySelector: jest.fn().mockReturnValue(null),
|
|
};
|
|
Object.defineProperty(global, 'document', {
|
|
value: documentMock,
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
process.env = originalEnv;
|
|
// Clean up document mock
|
|
Object.defineProperty(global, 'document', {
|
|
value: undefined,
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
});
|
|
|
|
describe('getStaticUrl', () => {
|
|
it('returns VITE_STATIC_URL if set', () => {
|
|
process.env.VITE_STATIC_URL = 'http://example.com/static.ged';
|
|
expect(getStaticUrl()).toBe('http://example.com/static.ged');
|
|
});
|
|
|
|
it('returns meta tag url if meta tag is present and valid', () => {
|
|
const mockMeta = {
|
|
getAttribute: jest.fn().mockReturnValue('http://example.com/meta.ged'),
|
|
};
|
|
documentMock.querySelector.mockReturnValue(mockMeta);
|
|
|
|
expect(getStaticUrl()).toBe('http://example.com/meta.ged');
|
|
expect(documentMock.querySelector).toHaveBeenCalledWith(
|
|
'meta[name="topola-static-url"]',
|
|
);
|
|
expect(mockMeta.getAttribute).toHaveBeenCalledWith('content');
|
|
});
|
|
|
|
it('ignores template placeholder in meta tag', () => {
|
|
const mockMeta = {
|
|
getAttribute: jest.fn().mockReturnValue('{{ env "STATIC_URL" }}'),
|
|
};
|
|
documentMock.querySelector.mockReturnValue(mockMeta);
|
|
|
|
expect(getStaticUrl()).toBeUndefined();
|
|
});
|
|
|
|
it('ignores __ placeholder in meta tag', () => {
|
|
const mockMeta = {
|
|
getAttribute: jest.fn().mockReturnValue('__STATIC_URL_PLACEHOLDER__'),
|
|
};
|
|
documentMock.querySelector.mockReturnValue(mockMeta);
|
|
|
|
expect(getStaticUrl()).toBeUndefined();
|
|
});
|
|
|
|
it('returns undefined if neither VITE_STATIC_URL nor meta tag is present', () => {
|
|
expect(getStaticUrl()).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('getParamFromSearch', () => {
|
|
it('extracts query param value', () => {
|
|
expect(getParamFromSearch('test', {test: 'val'})).toBe('val');
|
|
});
|
|
|
|
it('returns undefined if param is array or missing', () => {
|
|
expect(
|
|
getParamFromSearch('test', {test: ['val1', 'val2']}),
|
|
).toBeUndefined();
|
|
expect(getParamFromSearch('missing', {test: 'val'})).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('getArguments', () => {
|
|
const createLocation = (search: string): H.Location => ({
|
|
pathname: '/view',
|
|
search,
|
|
hash: '',
|
|
state: null,
|
|
key: '',
|
|
});
|
|
|
|
it('returns defaults for empty search', () => {
|
|
const args = getArguments(createLocation(''));
|
|
expect(args.sourceSpec).toBeUndefined();
|
|
expect(args.selection).toBeUndefined();
|
|
expect(args.detail).toBeUndefined();
|
|
expect(args.chartType).toBe(ChartType.Hourglass);
|
|
expect(args.standalone).toBe(true);
|
|
expect(args.showWikiTreeMenus).toBe(true);
|
|
expect(args.freezeAnimation).toBe(false);
|
|
expect(args.showSidePanel).toBe(true); // default on desktop
|
|
expect(args.config).toEqual({
|
|
color: ChartColors.COLOR_BY_GENERATION,
|
|
id: Ids.SHOW,
|
|
sex: Sex.SHOW,
|
|
place: PlaceDisplay.FULL,
|
|
placeCount: 2,
|
|
});
|
|
});
|
|
|
|
it('parses chart view types correctly', () => {
|
|
expect(getArguments(createLocation('?view=relatives')).chartType).toBe(
|
|
ChartType.Relatives,
|
|
);
|
|
expect(getArguments(createLocation('?view=fancy')).chartType).toBe(
|
|
ChartType.Fancy,
|
|
);
|
|
expect(getArguments(createLocation('?view=donatso')).chartType).toBe(
|
|
ChartType.Donatso,
|
|
);
|
|
expect(getArguments(createLocation('?view=unknown')).chartType).toBe(
|
|
ChartType.Hourglass,
|
|
);
|
|
});
|
|
|
|
it('parses WikiTree source spec', () => {
|
|
const args = getArguments(
|
|
createLocation('?source=wikitree&authcode=123'),
|
|
);
|
|
expect(args.sourceSpec).toEqual({
|
|
source: DataSourceEnum.WIKITREE,
|
|
authcode: '123',
|
|
});
|
|
});
|
|
|
|
it('parses Google Drive source spec', () => {
|
|
const args = getArguments(
|
|
createLocation('?source=google-drive&fileId=abc'),
|
|
);
|
|
expect(args.sourceSpec).toEqual({
|
|
source: DataSourceEnum.GOOGLE_DRIVE,
|
|
fileId: 'abc',
|
|
});
|
|
});
|
|
|
|
it('parses Uploaded source spec', () => {
|
|
const args = getArguments(createLocation('?file=hash123'));
|
|
expect(args.sourceSpec).toEqual({
|
|
source: DataSourceEnum.UPLOADED,
|
|
hash: 'hash123',
|
|
});
|
|
});
|
|
|
|
it('parses GEDCOM URL source spec', () => {
|
|
const args = getArguments(
|
|
createLocation('?url=http://example.com/tree.ged'),
|
|
);
|
|
expect(args.sourceSpec).toEqual({
|
|
source: DataSourceEnum.GEDCOM_URL,
|
|
url: 'http://example.com/tree.ged',
|
|
handleCors: true,
|
|
});
|
|
});
|
|
|
|
it('parses GEDCOM URL source spec with handleCors false', () => {
|
|
const args = getArguments(
|
|
createLocation('?url=http://example.com/tree.ged&handleCors=false'),
|
|
);
|
|
expect(args.sourceSpec).toEqual({
|
|
source: DataSourceEnum.GEDCOM_URL,
|
|
url: 'http://example.com/tree.ged',
|
|
handleCors: false,
|
|
});
|
|
});
|
|
|
|
it('parses Embedded source spec', () => {
|
|
const args = getArguments(createLocation('?embedded=true'));
|
|
expect(args.sourceSpec).toEqual({
|
|
source: DataSourceEnum.EMBEDDED,
|
|
});
|
|
});
|
|
|
|
it('prefers staticUrl over other source specs', () => {
|
|
process.env.VITE_STATIC_URL = 'http://example.com/static.ged';
|
|
const args = getArguments(
|
|
createLocation('?embedded=true&url=http://other.com'),
|
|
);
|
|
expect(args.sourceSpec).toEqual({
|
|
source: DataSourceEnum.GEDCOM_URL,
|
|
url: 'http://example.com/static.ged',
|
|
handleCors: false,
|
|
});
|
|
expect(args.standalone).toBe(false);
|
|
});
|
|
|
|
it('parses selection correctly', () => {
|
|
const args = getArguments(createLocation('?indi=I123&gen=4'));
|
|
expect(args.selection).toEqual({
|
|
id: 'I123',
|
|
generation: 4,
|
|
});
|
|
});
|
|
|
|
it('defaults selection generation to 0 if missing or invalid', () => {
|
|
const args1 = getArguments(createLocation('?indi=I123'));
|
|
expect(args1.selection).toEqual({
|
|
id: 'I123',
|
|
generation: 0,
|
|
});
|
|
|
|
const args2 = getArguments(createLocation('?indi=I123&gen=abc'));
|
|
expect(args2.selection).toEqual({
|
|
id: 'I123',
|
|
generation: 0,
|
|
});
|
|
});
|
|
|
|
it('parses detail parameter', () => {
|
|
const args = getArguments(createLocation('?detail=I456'));
|
|
expect(args.detail).toBe('I456');
|
|
});
|
|
|
|
it('parses showSidePanel setting', () => {
|
|
// Mock window.matchMedia for desktop
|
|
Object.defineProperty(global, 'window', {
|
|
value: {
|
|
matchMedia: jest.fn().mockReturnValue({matches: false}),
|
|
},
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
expect(getArguments(createLocation('')).showSidePanel).toBe(true);
|
|
expect(
|
|
getArguments(createLocation('?sidePanel=false')).showSidePanel,
|
|
).toBe(false);
|
|
|
|
// Mock window.matchMedia for mobile
|
|
Object.defineProperty(global, 'window', {
|
|
value: {
|
|
matchMedia: jest.fn().mockReturnValue({matches: true}),
|
|
},
|
|
writable: true,
|
|
configurable: true,
|
|
});
|
|
expect(getArguments(createLocation('')).showSidePanel).toBe(false);
|
|
expect(
|
|
getArguments(createLocation('?sidePanel=true')).showSidePanel,
|
|
).toBe(true);
|
|
});
|
|
|
|
it('parses boolean settings (standalone, showWikiTreeMenus, freeze)', () => {
|
|
const args = getArguments(
|
|
createLocation('?standalone=false&showWikiTreeMenus=false&freeze=true'),
|
|
);
|
|
expect(args.standalone).toBe(false);
|
|
expect(args.showWikiTreeMenus).toBe(false);
|
|
expect(args.freezeAnimation).toBe(true);
|
|
});
|
|
|
|
it('parses config object from query parameters', () => {
|
|
const args = getArguments(createLocation('?c=s&i=h&s=h&p=s&pn=5'));
|
|
expect(args.config).toEqual({
|
|
color: ChartColors.COLOR_BY_SEX,
|
|
id: Ids.HIDE,
|
|
sex: Sex.HIDE,
|
|
place: PlaceDisplay.SHORT,
|
|
placeCount: 5,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getUrlForArgs', () => {
|
|
const createLocation = (search: string): H.Location => ({
|
|
pathname: '/view',
|
|
search,
|
|
hash: '#hash-val',
|
|
state: null,
|
|
key: '',
|
|
});
|
|
|
|
it('updates query parameter value and preserves pathname/hash', () => {
|
|
const loc = createLocation('?param1=old¶m2=keep');
|
|
const updated = getUrlForArgs(loc, {param1: 'new', param3: 'added'});
|
|
expect(updated).toEqual({
|
|
pathname: '/view',
|
|
search: '?param1=new¶m2=keep¶m3=added',
|
|
hash: '#hash-val',
|
|
});
|
|
});
|
|
|
|
it('deletes query parameters set to null or undefined', () => {
|
|
const loc = createLocation('?param1=val1¶m2=val2');
|
|
const updated = getUrlForArgs(loc, {param1: null, param2: undefined});
|
|
expect(updated).toEqual({
|
|
pathname: '/view',
|
|
search: '',
|
|
hash: '#hash-val',
|
|
});
|
|
});
|
|
});
|
|
});
|