|
| 1 | +import { afterEach, describe, expect, it } from 'vitest' |
| 2 | + |
| 3 | +import { createReactQrl } from '../src' |
| 4 | +import { isReactActionRef, reactActionFromQrl } from '../src/action' |
| 5 | +import { parseQrl, resolveModuleUrl } from '../src/qrl' |
| 6 | +import { decodePropsFromAttribute, encodePropsForAttribute } from '../src/serialization' |
| 7 | + |
| 8 | +const runtimeHost = globalThis as { __FICT_MANIFEST__?: Record<string, string> } |
| 9 | + |
| 10 | +afterEach(() => { |
| 11 | + runtimeHost.__FICT_MANIFEST__ = undefined |
| 12 | +}) |
| 13 | + |
| 14 | +describe('qrl utils', () => { |
| 15 | + it('parses empty and malformed qrl strings safely', () => { |
| 16 | + expect(parseQrl('')).toEqual({ url: '', exportName: 'default' }) |
| 17 | + expect(parseQrl('#')).toEqual({ url: '', exportName: 'default' }) |
| 18 | + expect(parseQrl('##foo')).toEqual({ url: '#', exportName: 'foo' }) |
| 19 | + expect(parseQrl('/mod.ts#')).toEqual({ url: '/mod.ts', exportName: 'default' }) |
| 20 | + }) |
| 21 | + |
| 22 | + it('parses qrl strings with serialized suffix segments', () => { |
| 23 | + expect(parseQrl('/mod.ts#run[scope]')).toEqual({ url: '/mod.ts', exportName: 'run' }) |
| 24 | + }) |
| 25 | + |
| 26 | + it('resolves module URL through manifest mapping when present', () => { |
| 27 | + runtimeHost.__FICT_MANIFEST__ = { |
| 28 | + '/src/widget.tsx': '/assets/widget.abcd1234.js', |
| 29 | + } |
| 30 | + |
| 31 | + expect(resolveModuleUrl('/src/widget.tsx')).toBe('/assets/widget.abcd1234.js') |
| 32 | + expect(resolveModuleUrl('/src/missing.tsx')).toBe('/src/missing.tsx') |
| 33 | + }) |
| 34 | +}) |
| 35 | + |
| 36 | +describe('serialization utils', () => { |
| 37 | + it('round-trips serializable props through attribute payload encoding', () => { |
| 38 | + const input = { label: 'demo', count: 3, nested: { ok: true } } |
| 39 | + const encoded = encodePropsForAttribute(input) |
| 40 | + |
| 41 | + expect(decodePropsFromAttribute(encoded)).toEqual(input) |
| 42 | + }) |
| 43 | + |
| 44 | + it('returns empty object for missing, corrupted, and invalid payloads', () => { |
| 45 | + expect(decodePropsFromAttribute(null)).toEqual({}) |
| 46 | + expect(decodePropsFromAttribute('%')).toEqual({}) |
| 47 | + expect(decodePropsFromAttribute(encodeURIComponent('{not-json'))).toEqual({}) |
| 48 | + expect(decodePropsFromAttribute(encodeURIComponent('123'))).toEqual({}) |
| 49 | + }) |
| 50 | +}) |
| 51 | + |
| 52 | +describe('action ref guard', () => { |
| 53 | + it('accepts both marker-based and legacy action ref payloads', () => { |
| 54 | + expect(isReactActionRef(reactActionFromQrl('/mod.ts#run'))).toBe(true) |
| 55 | + expect(isReactActionRef({ __fictReactAction: '/legacy.ts#run' })).toBe(true) |
| 56 | + }) |
| 57 | + |
| 58 | + it('rejects non-action payloads', () => { |
| 59 | + expect(isReactActionRef({ __fictReactActionQrl: '/mod.ts#run' })).toBe(false) |
| 60 | + expect(isReactActionRef({ __fictReactAction: '/legacy.ts#run', extra: true })).toBe(false) |
| 61 | + expect(isReactActionRef(null)).toBe(false) |
| 62 | + expect(isReactActionRef('')).toBe(false) |
| 63 | + }) |
| 64 | +}) |
| 65 | + |
| 66 | +describe('createReactQrl', () => { |
| 67 | + it('creates qrl with default and explicit export names', () => { |
| 68 | + expect(createReactQrl('/virtual/module.ts')).toBe('/virtual/module.ts#default') |
| 69 | + expect(createReactQrl('/virtual/module.ts', 'Widget')).toBe('/virtual/module.ts#Widget') |
| 70 | + }) |
| 71 | +}) |
0 commit comments