mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-05 17:07:46 +08:00
fix: harden OAuth identity and cookies and correct quota and JSON display
This commit is contained in:
@@ -169,11 +169,6 @@ function getDisplayHtml(line: JsonDisplayLine): string {
|
||||
|
||||
function toggleFold(line: JsonDisplayLine, index: number) {
|
||||
const overrides = new Map(foldOverrides.value)
|
||||
if (line.collapsed) {
|
||||
for (const path of overrides.keys()) {
|
||||
if (path.startsWith(`${line.id}/`)) overrides.delete(path)
|
||||
}
|
||||
}
|
||||
overrides.set(line.id, !line.collapsed)
|
||||
foldOverrides.value = overrides
|
||||
localReader = undefined
|
||||
|
||||
@@ -10,23 +10,46 @@ const apps: Array<{ app: App, root: HTMLElement }> = []
|
||||
afterEach(() => { for (const { app, root } of apps.splice(0)) { app.unmount(); root.remove() } })
|
||||
|
||||
function mountBody(value: unknown) {
|
||||
const engine = new BodyDocumentEngine(value)
|
||||
const engine = new BodyDocumentEngine(value)
|
||||
const json = vi.fn(async (options: BodyJsonOptions) => engine.json(options))
|
||||
const bodyDocument = shallowRef({ json } as unknown as BodyDocument)
|
||||
const expandDepth = shallowRef(999)
|
||||
const errors: unknown[] = []
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp(defineComponent({ setup: () => () => h(JsonContent, {
|
||||
data: null, bodyDocument: bodyDocument.value, expandDepth: 999, viewMode: 'formatted', isDark: false, emptyMessage: '无数据',
|
||||
data: null, bodyDocument: bodyDocument.value, expandDepth: expandDepth.value, viewMode: 'formatted', isDark: false, emptyMessage: '无数据',
|
||||
onLoadError: (error: unknown) => errors.push(error),
|
||||
}) }))
|
||||
app.mount(root)
|
||||
apps.push({ app, root })
|
||||
return { root, json, bodyDocument, engine, errors }
|
||||
return { root, json, bodyDocument, expandDepth, engine, errors }
|
||||
}
|
||||
function button(root: HTMLElement, label: string) { return [...root.querySelectorAll('button')].find(button => button.textContent?.includes(label))! }
|
||||
|
||||
describe('worker-backed body views', () => {
|
||||
it('resets every layer on collapse-all and reopens worker-backed nodes one layer at a time', async () => {
|
||||
const value = { messages: [{ content: { text: 'deep worker content' } }] }
|
||||
const { root, json, expandDepth, engine } = mountBody(value)
|
||||
await vi.waitFor(() => expect(root.textContent).toContain('deep worker content'))
|
||||
for (let cycle = 0; cycle < 2; cycle += 1) {
|
||||
expandDepth.value = 0
|
||||
await vi.waitFor(() => expect(root.querySelectorAll('.json-line')).toHaveLength(3))
|
||||
expect(json).toHaveBeenLastCalledWith(expect.objectContaining({ expandDepth: 0, foldOverrides: new Map() }))
|
||||
for (const lineCount of [5, 7, 9]) {
|
||||
expect(root.textContent).not.toContain('deep worker content')
|
||||
root.querySelector<HTMLButtonElement>('button[aria-label="展开节点"]')!.click()
|
||||
await vi.waitFor(() => expect(root.querySelectorAll('.json-line')).toHaveLength(lineCount))
|
||||
}
|
||||
expect(root.textContent).toContain('deep worker content')
|
||||
expandDepth.value = 999
|
||||
await vi.waitFor(() => expect(json).toHaveBeenLastCalledWith(expect.objectContaining({ expandDepth: 999, foldOverrides: new Map() })))
|
||||
await vi.waitFor(() => expect(root.textContent).toContain('deep worker content'))
|
||||
expect(root.querySelector('button[aria-label="展开节点"]')).toBeNull()
|
||||
}
|
||||
expect(JSON.parse(engine.copy())).toEqual(value)
|
||||
})
|
||||
|
||||
it('automatically reads worker chunks while scrolling and preserves the complete copy', async () => {
|
||||
const values = Array.from({ length: 1000 }, (_value, index) => `value-${index}`)
|
||||
const { root, json, engine } = mountBody(values)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { createApp, defineComponent, h, shallowRef, type App } from 'vue'
|
||||
import JsonContent from '../RequestDetailDrawer/JsonContent.vue'
|
||||
import JsonContentPanel from '../JsonContentPanel.vue'
|
||||
import { JSON_PAGE_SIZE, JSON_SCROLL_CHUNK_SIZE, JSON_TEXT_CHUNK_SIZE } from '../../utils/json-viewer'
|
||||
|
||||
const mountedApps: Array<{ app: App, root: HTMLElement }> = []
|
||||
@@ -48,15 +49,57 @@ describe('JsonContent lazy rendering', () => {
|
||||
expect(root.querySelector<HTMLElement>('.virtual-body-scroll')?.scrollTop).toBe(0)
|
||||
})
|
||||
|
||||
it('opens the complete subtree with one bracket click', async () => {
|
||||
const { root } = mountJson({ messages: [{ content: 'hidden content' }] }, 0)
|
||||
it('opens one layer per bracket click after collapsing all descendants', async () => {
|
||||
const { root } = mountJson({ messages: [{ content: { text: 'hidden content' } }] }, 0)
|
||||
await vi.waitFor(() => expect(root.querySelectorAll('.json-line')).toHaveLength(3))
|
||||
expect(root.textContent).not.toContain('hidden content')
|
||||
root.querySelector<HTMLButtonElement>('button[aria-label="展开节点"]')!.click()
|
||||
for (const lineCount of [5, 7, 9]) {
|
||||
expect(root.textContent).not.toContain('hidden content')
|
||||
root.querySelector<HTMLElement>('.line-content.clickable-collapsed')!.click()
|
||||
await vi.waitFor(() => expect(root.querySelectorAll('.json-line')).toHaveLength(lineCount))
|
||||
}
|
||||
await vi.waitFor(() => expect(root.textContent).toContain('hidden content'))
|
||||
expect(root.querySelector('button[aria-label="展开节点"]')).toBeNull()
|
||||
})
|
||||
|
||||
it('collapses every layer through the toolbar and keeps expand-all working', async () => {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const app = createApp(defineComponent({ setup: () => () => h(JsonContentPanel, {
|
||||
data: { messages: [{ content: { text: 'deep content' } }] }, isDark: false,
|
||||
}) }))
|
||||
app.mount(root)
|
||||
mountedApps.push({ app, root })
|
||||
await vi.waitFor(() => expect(root.querySelectorAll('.json-line')).toHaveLength(3))
|
||||
for (let cycle = 0; cycle < 2; cycle += 1) {
|
||||
root.querySelector<HTMLButtonElement>('button[title="展开全部"]')!.click()
|
||||
await vi.waitFor(() => expect(root.textContent).toContain('deep content'))
|
||||
expect(root.querySelector('button[aria-label="展开节点"]')).toBeNull()
|
||||
root.querySelector<HTMLButtonElement>('button[title="收缩全部"]')!.click()
|
||||
await vi.waitFor(() => expect(root.querySelectorAll('.json-line')).toHaveLength(3))
|
||||
for (const lineCount of [5, 7]) {
|
||||
root.querySelector<HTMLButtonElement>('button[aria-label="展开节点"]')!.click()
|
||||
await vi.waitFor(() => expect(root.querySelectorAll('.json-line')).toHaveLength(lineCount))
|
||||
expect(root.textContent).not.toContain('deep content')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('preserves an explicitly folded child when its parent is reopened', async () => {
|
||||
const { root } = mountJson({ messages: [{ content: { text: 'hidden content' } }] })
|
||||
const nodeButton = (key: string) => [...root.querySelectorAll('.json-line')]
|
||||
.find(line => line.querySelector('.token-key')?.textContent === JSON.stringify(key))!
|
||||
.querySelector<HTMLButtonElement>('button')!
|
||||
await vi.waitFor(() => expect(root.textContent).toContain('hidden content'))
|
||||
nodeButton('content').click()
|
||||
await vi.waitFor(() => expect(nodeButton('content').getAttribute('aria-expanded')).toBe('false'))
|
||||
nodeButton('messages').click()
|
||||
await vi.waitFor(() => expect(nodeButton('messages').getAttribute('aria-expanded')).toBe('false'))
|
||||
nodeButton('messages').click()
|
||||
await vi.waitFor(() => expect(nodeButton('messages').getAttribute('aria-expanded')).toBe('true'))
|
||||
expect(nodeButton('content').getAttribute('aria-expanded')).toBe('false')
|
||||
expect(root.textContent).not.toContain('hidden content')
|
||||
})
|
||||
|
||||
it('shows complete long strings without an extra expansion button', async () => {
|
||||
const content = `${'x'.repeat(JSON_TEXT_CHUNK_SIZE * 10) }末尾🙂`
|
||||
const { root, data } = mountJson({ content })
|
||||
|
||||
@@ -65,11 +65,34 @@ describe('lazy JSON pages', () => {
|
||||
expect(reader.read(21).lines).toEqual([])
|
||||
})
|
||||
|
||||
it('expands all descendants of an opened node while honoring an explicitly closed child', () => {
|
||||
it.each([false, true])('opens collapsed descendants one layer at a time with indexPaths=%s', indexPaths => {
|
||||
const data = { messages: [{ content: { text: 'complete content' } }] }
|
||||
const opened = getJsonPage(data, { expandDepth: 1, foldOverrides: new Map([['$/messages', false]]) })
|
||||
const paths = indexPaths ? ['$/0', '$/0/0', '$/0/0/0'] : ['$/messages', '$/messages/0', '$/messages/0/content']
|
||||
const foldOverrides = new Map<string, boolean>()
|
||||
for (const path of paths) {
|
||||
const folded = getJsonPage(data, { expandDepth: 0, foldOverrides, indexPaths })
|
||||
expect(folded.lines.find(line => line.id === path)).toMatchObject({ collapsed: true })
|
||||
expect(folded.lines.some(line => line.value === 'complete content')).toBe(false)
|
||||
foldOverrides.set(path, false)
|
||||
}
|
||||
const opened = getJsonPage(data, { expandDepth: 0, foldOverrides, indexPaths })
|
||||
expect(opened.lines.some(line => line.value === 'complete content')).toBe(true)
|
||||
const folded = getJsonPage(data, { expandDepth: 1, foldOverrides: new Map([['$/messages', false], ['$/messages/0/content', true]]) })
|
||||
})
|
||||
|
||||
it('does not read unopened descendants after opening their parent', () => {
|
||||
const readContent = vi.fn(() => { throw new Error('unopened content must not be read') })
|
||||
const message = Object.defineProperty({}, 'content', { enumerable: true, get: readContent })
|
||||
const opened = getJsonPage({ messages: [message] }, { expandDepth: 0, foldOverrides: new Map([['$/messages', false]]) })
|
||||
expect(opened.lines.find(line => line.id === '$/messages/0')).toMatchObject({ collapsed: true })
|
||||
expect(readContent).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('still expands every layer in expand-all mode while honoring explicitly closed children', () => {
|
||||
const data = { messages: [{ content: { text: 'complete content' } }] }
|
||||
const expanded = getJsonPage(data, { expandDepth: 999 })
|
||||
expect(expanded.lines.some(line => line.value === 'complete content')).toBe(true)
|
||||
expect(expanded.lines.filter(line => line.canFold).every(line => !line.collapsed)).toBe(true)
|
||||
const folded = getJsonPage(data, { expandDepth: 999, foldOverrides: new Map([['$/messages', false], ['$/messages/0/content', true]]) })
|
||||
expect(folded.lines.some(line => line.value === 'complete content')).toBe(false)
|
||||
})
|
||||
|
||||
|
||||
@@ -130,7 +130,7 @@ function* splitJsonLine(line: JsonTreeLine, chunkSize: number): Generator<JsonTr
|
||||
function* walkJsonLines(data: unknown, options: JsonPageOptions): Generator<JsonDisplayLine> {
|
||||
const depth = options.expandDepth === 0 || options.expandDepth == null ? 1 : options.expandDepth
|
||||
|
||||
function* walk(value: unknown, path: string, indent: number, comma: string, key?: string, expandedParent = false): Generator<JsonTreeLine> {
|
||||
function* walk(value: unknown, path: string, indent: number, comma: string, key?: string): Generator<JsonTreeLine> {
|
||||
if (value == null || typeof value !== 'object') {
|
||||
yield { id: path, indent, key, value, comma, canFold: false, collapsed: false }
|
||||
return
|
||||
@@ -140,8 +140,7 @@ function* walkJsonLines(data: unknown, options: JsonPageOptions): Generator<Json
|
||||
const keys = isArray ? [] : Object.keys(value)
|
||||
const childCount = isArray ? value.length : keys.length
|
||||
const override = options.foldOverrides?.get(path)
|
||||
const expandedSubtree = expandedParent || override === false
|
||||
const collapsed = childCount === 0 || (override ?? (!expandedSubtree && indent >= depth))
|
||||
const collapsed = childCount === 0 || (override ?? (indent >= depth))
|
||||
const bracket = isArray ? '[' : '{'
|
||||
const closingBracket = isArray ? ']' : '}'
|
||||
yield { id: path, indent, key, comma, bracket, closingBracket, childCount, isArray, collapsed, canFold: childCount > 0 }
|
||||
@@ -151,7 +150,7 @@ function* walkJsonLines(data: unknown, options: JsonPageOptions): Generator<Json
|
||||
const childKey = isArray ? String(index) : keys[index]
|
||||
const childPath = `${path}/${options.indexPaths ? index : childKey.replace(/~/g, '~0').replace(/\//g, '~1')}`
|
||||
const childValue = isArray ? value[index] : (value as Record<string, unknown>)[childKey]
|
||||
yield* walk(childValue, childPath, indent + 1, index === childCount - 1 ? '' : ',', isArray ? undefined : childKey, expandedSubtree)
|
||||
yield* walk(childValue, childPath, indent + 1, index === childCount - 1 ? '' : ',', isArray ? undefined : childKey)
|
||||
}
|
||||
yield { id: `close:${path}`, indent, bracket: closingBracket, comma, canFold: false, collapsed: false }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user