client-sources.client.spec.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. /** Client-face source catalog behavior. */
  2. import { describe, expect, it } from 'vitest'
  3. import { ClientSourceCatalog } from '../src/client/cdp/sources.ts'
  4. import { inspectorId } from '../src/shared/bridge/ids.ts'
  5. const scriptKey = inspectorId<'RuntimeScriptKey'>('bundle', 'scriptKey')
  6. describe('Client source catalog', () => {
  7. it('describes scripts and transfers UTF-8 source and maps in bounded chunks', async () => {
  8. const source = 'const greeting = "你好"\nconsole.log(greeting)\n'
  9. const sourceMap = JSON.stringify({ version: 3, sources: ['client.ts'], mappings: 'AAAA' })
  10. const catalog = new ClientSourceCatalog([{
  11. scriptKey,
  12. url: 'http://client.test/plugins/inspector/client.js?rev=abc',
  13. hash: 'abc',
  14. sourceMapUrl: 'http://client.test/plugins/inspector/client.js.map?rev=abc',
  15. isModule: false,
  16. loadSource: async () => source,
  17. loadSourceMap: async () => sourceMap,
  18. }])
  19. await expect(catalog.execute({ op: 'list-scripts' }, 1_024)).resolves.toEqual({
  20. op: 'list-scripts',
  21. scripts: [{
  22. scriptKey,
  23. url: 'http://client.test/plugins/inspector/client.js?rev=abc',
  24. hash: 'abc',
  25. buildId: '',
  26. sourceMapUrl: 'http://client.test/plugins/inspector/client.js.map?rev=abc',
  27. startLine: 0,
  28. startColumn: 0,
  29. endLine: 2,
  30. endColumn: 0,
  31. isModule: false,
  32. length: source.length,
  33. }],
  34. })
  35. const bytes: Uint8Array[] = []
  36. let offset = 0
  37. while (true) {
  38. const result = await catalog.execute({
  39. op: 'get-content-chunk',
  40. scriptKey,
  41. content: 'source',
  42. offset,
  43. maxBytes: 7,
  44. }, 1_024)
  45. if (result.op !== 'get-content-chunk' || !result.available) throw new Error('missing source chunk')
  46. bytes.push(Uint8Array.from(atob(result.data), character => character.charCodeAt(0)))
  47. offset = result.nextOffset
  48. if (result.eof) break
  49. }
  50. const combined = new Uint8Array(bytes.reduce((total, chunk) => total + chunk.byteLength, 0))
  51. let cursor = 0
  52. for (const chunk of bytes) {
  53. combined.set(chunk, cursor)
  54. cursor += chunk.byteLength
  55. }
  56. expect(new TextDecoder().decode(combined)).toBe(source)
  57. const map = await catalog.execute({
  58. op: 'get-content-chunk',
  59. scriptKey,
  60. content: 'source-map',
  61. offset: 0,
  62. maxBytes: 1_024,
  63. }, 1_024)
  64. if (map.op !== 'get-content-chunk' || !map.available) throw new Error('missing source map')
  65. expect(new TextDecoder().decode(Uint8Array.from(atob(map.data), character => character.charCodeAt(0))))
  66. .toBe(sourceMap)
  67. })
  68. it('rejects assets above the configured aggregate limit', async () => {
  69. const catalog = new ClientSourceCatalog([{
  70. scriptKey,
  71. url: 'http://client.test/client.js',
  72. hash: 'abc',
  73. loadSource: async () => 'x'.repeat(101),
  74. }])
  75. await expect(catalog.execute({ op: 'list-scripts' }, 100)).rejects.toMatchObject({ code: 'result-too-large' })
  76. })
  77. })