fix desktop doubao history cleanup
This commit is contained in:
@@ -13,6 +13,7 @@ const {
|
||||
isNonCitationAssetDomain,
|
||||
isNonCitationAssetUrl,
|
||||
hasDoubaoFinishedStreamCapture,
|
||||
deleteDoubaoCurrentConversationInPage,
|
||||
parseDoubaoCaptures,
|
||||
parseDoubaoStreamBody,
|
||||
resolveDoubaoSubmissionAnswer,
|
||||
@@ -140,6 +141,660 @@ describe('doubao adapter helpers', () => {
|
||||
expect(doubaoModeProviderModel('expert')).toBe('doubao-web-fast')
|
||||
})
|
||||
|
||||
it('clicks through Doubao current conversation deletion controls', async () => {
|
||||
const previousWindow = globalThis.window
|
||||
const previousDocument = globalThis.document
|
||||
const previousHTMLElement = globalThis.HTMLElement
|
||||
const previousMouseEvent = globalThis.MouseEvent
|
||||
const previousURL = globalThis.URL
|
||||
|
||||
const makeElement = (input: {
|
||||
tagName?: string
|
||||
text?: string
|
||||
attrs?: Record<string, string>
|
||||
rect?: { left: number; top: number; width: number; height: number }
|
||||
visible?: boolean
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
children?: MockElement[]
|
||||
}): MockElement => new MockElement(input)
|
||||
|
||||
class MockMouseEvent {
|
||||
readonly type: string
|
||||
constructor(type: string) {
|
||||
this.type = type
|
||||
}
|
||||
}
|
||||
|
||||
class MockElement {
|
||||
tagName: string
|
||||
innerText: string
|
||||
textContent: string
|
||||
className: string
|
||||
parentElement: MockElement | null = null
|
||||
children: MockElement[] = []
|
||||
tabIndex = -1
|
||||
style = {
|
||||
display: 'block',
|
||||
visibility: 'visible',
|
||||
opacity: '1',
|
||||
cursor: 'default',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0)',
|
||||
}
|
||||
|
||||
private readonly attrs: Map<string, string>
|
||||
private readonly rect: { left: number; top: number; width: number; height: number }
|
||||
private readonly onClick?: () => void
|
||||
|
||||
constructor(input: {
|
||||
tagName?: string
|
||||
text?: string
|
||||
attrs?: Record<string, string>
|
||||
rect?: { left: number; top: number; width: number; height: number }
|
||||
visible?: boolean
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
children?: MockElement[]
|
||||
}) {
|
||||
this.tagName = (input.tagName ?? 'div').toUpperCase()
|
||||
this.innerText = input.text ?? ''
|
||||
this.textContent = input.text ?? ''
|
||||
this.className = input.className ?? ''
|
||||
this.attrs = new Map(Object.entries(input.attrs ?? {}))
|
||||
this.rect = input.rect ?? { left: 0, top: 0, width: 120, height: 32 }
|
||||
this.onClick = input.onClick
|
||||
if (input.visible === false) {
|
||||
this.style.display = 'none'
|
||||
}
|
||||
for (const child of input.children ?? []) {
|
||||
this.appendChild(child)
|
||||
}
|
||||
}
|
||||
|
||||
appendChild(child: MockElement): void {
|
||||
child.parentElement = this
|
||||
this.children.push(child)
|
||||
}
|
||||
|
||||
getAttribute(name: string): string | null {
|
||||
return this.attrs.get(name) ?? null
|
||||
}
|
||||
|
||||
hasAttribute(name: string): boolean {
|
||||
return this.attrs.has(name)
|
||||
}
|
||||
|
||||
querySelector(selector: string): MockElement | null {
|
||||
return this.querySelectorAll(selector)[0] ?? null
|
||||
}
|
||||
|
||||
querySelectorAll(selector: string): MockElement[] {
|
||||
const selectors = selector
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
const results: MockElement[] = []
|
||||
const visit = (node: MockElement) => {
|
||||
for (const child of node.children) {
|
||||
if (selectors.some((item) => matchesSelector(child, item))) {
|
||||
results.push(child)
|
||||
}
|
||||
visit(child)
|
||||
}
|
||||
}
|
||||
visit(this)
|
||||
return results
|
||||
}
|
||||
|
||||
getBoundingClientRect(): {
|
||||
left: number
|
||||
top: number
|
||||
width: number
|
||||
height: number
|
||||
right: number
|
||||
bottom: number
|
||||
} {
|
||||
let current: MockElement | null = this
|
||||
while (current) {
|
||||
if (current.style.display === 'none' || current.style.visibility === 'hidden') {
|
||||
return {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
}
|
||||
current = current.parentElement
|
||||
}
|
||||
return {
|
||||
...this.rect,
|
||||
right: this.rect.left + this.rect.width,
|
||||
bottom: this.rect.top + this.rect.height,
|
||||
}
|
||||
}
|
||||
|
||||
scrollIntoView(): void {}
|
||||
|
||||
dispatchEvent(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
click(): void {
|
||||
this.onClick?.()
|
||||
}
|
||||
}
|
||||
|
||||
const matchesSelector = (element: MockElement, selector: string): boolean => {
|
||||
if (selector === '*') {
|
||||
return true
|
||||
}
|
||||
if (/^[a-z]+$/i.test(selector)) {
|
||||
return element.tagName.toLowerCase() === selector.toLowerCase()
|
||||
}
|
||||
const tagMatch = selector.match(/^([a-z]+)(.+)$/i)
|
||||
if (tagMatch) {
|
||||
return (
|
||||
element.tagName.toLowerCase() === tagMatch[1]?.toLowerCase() &&
|
||||
matchesSelector(element, tagMatch[2] ?? '')
|
||||
)
|
||||
}
|
||||
const attrMatch = selector.match(/^\[([^=\]~*^$]+)(?:[*^$]?=['"]?([^'"\]]+)['"]?)?\]$/)
|
||||
if (attrMatch) {
|
||||
const attr = attrMatch[1] ?? ''
|
||||
const expected = attrMatch[2]
|
||||
const value = element.getAttribute(attr)
|
||||
if (expected === undefined) {
|
||||
return value !== null
|
||||
}
|
||||
if (selector.includes('*=')) {
|
||||
return value?.includes(expected) ?? false
|
||||
}
|
||||
return value === expected
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const documentRoot = makeElement({ rect: { left: 0, top: 0, width: 1280, height: 900 } })
|
||||
const body = makeElement({ rect: { left: 0, top: 0, width: 1280, height: 900 } })
|
||||
documentRoot.appendChild(body)
|
||||
|
||||
let dialog: MockElement | null = null
|
||||
let currentHistory: MockElement | null = null
|
||||
let confirmed = false
|
||||
|
||||
const confirmButton = makeElement({
|
||||
tagName: 'button',
|
||||
text: '删除',
|
||||
attrs: { role: 'button' },
|
||||
rect: { left: 680, top: 478, width: 80, height: 32 },
|
||||
onClick: () => {
|
||||
confirmed = true
|
||||
if (dialog) {
|
||||
body.children = body.children.filter((child) => child !== dialog)
|
||||
}
|
||||
if (currentHistory) {
|
||||
body.children = body.children.filter((child) => child !== currentHistory)
|
||||
}
|
||||
},
|
||||
})
|
||||
dialog = makeElement({
|
||||
tagName: 'div',
|
||||
text: '确定删除对话 删除后不可恢复 取消 删除',
|
||||
attrs: { role: 'dialog', 'aria-modal': 'true' },
|
||||
rect: { left: 440, top: 320, width: 400, height: 220 },
|
||||
visible: false,
|
||||
children: [
|
||||
makeElement({
|
||||
tagName: 'button',
|
||||
text: '取消',
|
||||
attrs: { role: 'button' },
|
||||
rect: { left: 580, top: 478, width: 80, height: 32 },
|
||||
}),
|
||||
confirmButton,
|
||||
],
|
||||
})
|
||||
|
||||
const deleteItem = makeElement({
|
||||
tagName: 'div',
|
||||
text: '删除',
|
||||
attrs: { role: 'menuitem' },
|
||||
rect: { left: 240, top: 124, width: 96, height: 32 },
|
||||
onClick: () => {
|
||||
dialog!.style.display = 'block'
|
||||
},
|
||||
})
|
||||
const menu = makeElement({
|
||||
tagName: 'div',
|
||||
text: '删除',
|
||||
className: 'dropdown-menu',
|
||||
rect: { left: 228, top: 112, width: 128, height: 48 },
|
||||
visible: false,
|
||||
children: [deleteItem],
|
||||
})
|
||||
|
||||
const moreButton = makeElement({
|
||||
tagName: 'button',
|
||||
text: '...',
|
||||
attrs: { 'aria-label': '更多', role: 'button' },
|
||||
rect: { left: 252, top: 74, width: 28, height: 28 },
|
||||
onClick: () => {
|
||||
menu.style.display = 'block'
|
||||
deleteItem.style.display = 'block'
|
||||
},
|
||||
})
|
||||
currentHistory = makeElement({
|
||||
tagName: 'div',
|
||||
text: '室内门锁供货商',
|
||||
attrs: { 'data-active': 'true' },
|
||||
rect: { left: 16, top: 64, width: 272, height: 48 },
|
||||
className: 'history-item active',
|
||||
children: [
|
||||
makeElement({
|
||||
tagName: 'a',
|
||||
text: '室内门锁供货商',
|
||||
attrs: { href: '/chat/local_4995117315817271' },
|
||||
rect: { left: 24, top: 72, width: 210, height: 28 },
|
||||
}),
|
||||
moreButton,
|
||||
],
|
||||
})
|
||||
|
||||
body.appendChild(currentHistory)
|
||||
body.appendChild(menu)
|
||||
body.appendChild(dialog)
|
||||
|
||||
const documentMock = {
|
||||
body,
|
||||
querySelectorAll: (selector: string) => body.querySelectorAll(selector),
|
||||
querySelector: (selector: string) => body.querySelector(selector),
|
||||
}
|
||||
const windowMock = {
|
||||
location: {
|
||||
href: 'https://www.doubao.com/chat/local_4995117315817271',
|
||||
origin: 'https://www.doubao.com',
|
||||
},
|
||||
innerWidth: 1280,
|
||||
innerHeight: 900,
|
||||
setTimeout: (callback: () => void) => {
|
||||
callback()
|
||||
return 1
|
||||
},
|
||||
getComputedStyle: (element: MockElement) => element.style,
|
||||
}
|
||||
|
||||
try {
|
||||
const mutableGlobal = globalThis as Record<string, unknown>
|
||||
mutableGlobal.window = windowMock
|
||||
mutableGlobal.document = documentMock
|
||||
mutableGlobal.HTMLElement = MockElement
|
||||
mutableGlobal.MouseEvent = MockMouseEvent
|
||||
mutableGlobal.URL = URL
|
||||
|
||||
const result = await deleteDoubaoCurrentConversationInPage({
|
||||
conversationId: 'local_4995117315817271',
|
||||
pageUrl: 'https://www.doubao.com/chat/local_4995117315817271',
|
||||
title: '豆包',
|
||||
})
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
conversation_id: 'local_4995117315817271',
|
||||
menu_clicked: true,
|
||||
delete_clicked: true,
|
||||
confirm_clicked: true,
|
||||
})
|
||||
expect(confirmed).toBe(true)
|
||||
} finally {
|
||||
const mutableGlobal = globalThis as Record<string, unknown>
|
||||
mutableGlobal.window = previousWindow
|
||||
mutableGlobal.document = previousDocument
|
||||
mutableGlobal.HTMLElement = previousHTMLElement
|
||||
mutableGlobal.MouseEvent = previousMouseEvent
|
||||
mutableGlobal.URL = previousURL
|
||||
}
|
||||
})
|
||||
|
||||
it('uses the generated Doubao history link when cleanup only has the question text', async () => {
|
||||
const previousWindow = globalThis.window
|
||||
const previousDocument = globalThis.document
|
||||
const previousHTMLElement = globalThis.HTMLElement
|
||||
const previousMouseEvent = globalThis.MouseEvent
|
||||
const previousURL = globalThis.URL
|
||||
|
||||
const makeElement = (input: {
|
||||
tagName?: string
|
||||
text?: string
|
||||
attrs?: Record<string, string>
|
||||
rect?: { left: number; top: number; width: number; height: number }
|
||||
visible?: boolean
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
children?: MockElement[]
|
||||
}): MockElement => new MockElement(input)
|
||||
|
||||
class MockMouseEvent {
|
||||
readonly type: string
|
||||
constructor(type: string) {
|
||||
this.type = type
|
||||
}
|
||||
}
|
||||
|
||||
class MockElement {
|
||||
tagName: string
|
||||
innerText: string
|
||||
textContent: string
|
||||
className: string
|
||||
parentElement: MockElement | null = null
|
||||
children: MockElement[] = []
|
||||
tabIndex = -1
|
||||
style = {
|
||||
display: 'block',
|
||||
visibility: 'visible',
|
||||
opacity: '1',
|
||||
cursor: 'pointer',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0)',
|
||||
}
|
||||
|
||||
private readonly attrs: Map<string, string>
|
||||
private readonly rect: { left: number; top: number; width: number; height: number }
|
||||
private readonly onClick?: () => void
|
||||
|
||||
constructor(input: {
|
||||
tagName?: string
|
||||
text?: string
|
||||
attrs?: Record<string, string>
|
||||
rect?: { left: number; top: number; width: number; height: number }
|
||||
visible?: boolean
|
||||
className?: string
|
||||
onClick?: () => void
|
||||
children?: MockElement[]
|
||||
}) {
|
||||
this.tagName = (input.tagName ?? 'div').toUpperCase()
|
||||
this.innerText = input.text ?? ''
|
||||
this.textContent = input.text ?? ''
|
||||
this.className = input.className ?? ''
|
||||
this.attrs = new Map(Object.entries(input.attrs ?? {}))
|
||||
this.rect = input.rect ?? { left: 0, top: 0, width: 120, height: 32 }
|
||||
this.onClick = input.onClick
|
||||
if (input.visible === false) {
|
||||
this.style.display = 'none'
|
||||
}
|
||||
for (const child of input.children ?? []) {
|
||||
this.appendChild(child)
|
||||
}
|
||||
}
|
||||
|
||||
appendChild(child: MockElement): void {
|
||||
child.parentElement = this
|
||||
this.children.push(child)
|
||||
}
|
||||
|
||||
getAttribute(name: string): string | null {
|
||||
return this.attrs.get(name) ?? null
|
||||
}
|
||||
|
||||
hasAttribute(name: string): boolean {
|
||||
return this.attrs.has(name)
|
||||
}
|
||||
|
||||
querySelector(selector: string): MockElement | null {
|
||||
return this.querySelectorAll(selector)[0] ?? null
|
||||
}
|
||||
|
||||
querySelectorAll(selector: string): MockElement[] {
|
||||
const selectors = selector
|
||||
.split(',')
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
const results: MockElement[] = []
|
||||
const visit = (node: MockElement) => {
|
||||
for (const child of node.children) {
|
||||
if (selectors.some((item) => matchesSelector(child, item))) {
|
||||
results.push(child)
|
||||
}
|
||||
visit(child)
|
||||
}
|
||||
}
|
||||
visit(this)
|
||||
return results
|
||||
}
|
||||
|
||||
getBoundingClientRect(): {
|
||||
left: number
|
||||
top: number
|
||||
width: number
|
||||
height: number
|
||||
right: number
|
||||
bottom: number
|
||||
} {
|
||||
let current: MockElement | null = this
|
||||
while (current) {
|
||||
if (current.style.display === 'none' || current.style.visibility === 'hidden') {
|
||||
return {
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
}
|
||||
current = current.parentElement
|
||||
}
|
||||
return {
|
||||
...this.rect,
|
||||
right: this.rect.left + this.rect.width,
|
||||
bottom: this.rect.top + this.rect.height,
|
||||
}
|
||||
}
|
||||
|
||||
scrollIntoView(): void {}
|
||||
|
||||
focus(): void {}
|
||||
|
||||
dispatchEvent(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
click(): void {
|
||||
this.onClick?.()
|
||||
}
|
||||
}
|
||||
|
||||
const matchesSelector = (element: MockElement, selector: string): boolean => {
|
||||
if (selector === '*') {
|
||||
return true
|
||||
}
|
||||
if (selector.includes(' ')) {
|
||||
const parts = selector.split(/\s+/).filter(Boolean)
|
||||
const last = parts.at(-1)
|
||||
return last ? matchesSelector(element, last) : false
|
||||
}
|
||||
if (/^[a-z]+$/i.test(selector)) {
|
||||
return element.tagName.toLowerCase() === selector.toLowerCase()
|
||||
}
|
||||
const tagMatch = selector.match(/^([a-z]+)(.+)$/i)
|
||||
if (tagMatch) {
|
||||
return (
|
||||
element.tagName.toLowerCase() === tagMatch[1]?.toLowerCase() &&
|
||||
matchesSelector(element, tagMatch[2] ?? '')
|
||||
)
|
||||
}
|
||||
const attrMatch = selector.match(/^\[([^=\]~*^$]+)([*^$]?=)?['"]?([^'"\]]*)['"]?\]$/)
|
||||
if (attrMatch) {
|
||||
const attr = attrMatch[1] ?? ''
|
||||
const operator = attrMatch[2]
|
||||
const expected = attrMatch[3] ?? ''
|
||||
const value = element.getAttribute(attr)
|
||||
if (!operator) {
|
||||
return value !== null
|
||||
}
|
||||
if (operator === '*=') {
|
||||
return value?.includes(expected) ?? false
|
||||
}
|
||||
if (operator === '^=') {
|
||||
return value?.startsWith(expected) ?? false
|
||||
}
|
||||
return value === expected
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
const documentRoot = makeElement({ rect: { left: 0, top: 0, width: 1280, height: 900 } })
|
||||
const body = makeElement({ rect: { left: 0, top: 0, width: 1280, height: 900 } })
|
||||
documentRoot.appendChild(body)
|
||||
|
||||
let dialog: MockElement | null = null
|
||||
let currentHistory: MockElement | null = null
|
||||
let nextHistory: MockElement | null = null
|
||||
|
||||
const confirmButton = makeElement({
|
||||
tagName: 'button',
|
||||
text: '删除',
|
||||
attrs: { role: 'button' },
|
||||
rect: { left: 680, top: 478, width: 80, height: 32 },
|
||||
onClick: () => {
|
||||
if (dialog) {
|
||||
body.children = body.children.filter((child) => child !== dialog)
|
||||
}
|
||||
if (currentHistory) {
|
||||
body.children = body.children.filter((child) => child !== currentHistory)
|
||||
}
|
||||
},
|
||||
})
|
||||
dialog = makeElement({
|
||||
tagName: 'div',
|
||||
text: '确定删除对话 删除后不可恢复 取消 删除',
|
||||
attrs: { role: 'dialog', 'aria-modal': 'true' },
|
||||
rect: { left: 440, top: 320, width: 400, height: 220 },
|
||||
visible: false,
|
||||
children: [
|
||||
makeElement({
|
||||
tagName: 'button',
|
||||
text: '取消',
|
||||
attrs: { role: 'button' },
|
||||
rect: { left: 580, top: 478, width: 80, height: 32 },
|
||||
}),
|
||||
confirmButton,
|
||||
],
|
||||
})
|
||||
|
||||
const deleteItem = makeElement({
|
||||
tagName: 'div',
|
||||
text: '删除',
|
||||
attrs: { role: 'menuitem' },
|
||||
rect: { left: 240, top: 124, width: 96, height: 32 },
|
||||
visible: false,
|
||||
onClick: () => {
|
||||
dialog!.style.display = 'block'
|
||||
},
|
||||
})
|
||||
const menu = makeElement({
|
||||
tagName: 'div',
|
||||
text: '删除',
|
||||
attrs: { role: 'menu' },
|
||||
className: 'dropdown-menu',
|
||||
rect: { left: 228, top: 112, width: 128, height: 48 },
|
||||
children: [deleteItem],
|
||||
})
|
||||
|
||||
const moreButton = makeElement({
|
||||
tagName: 'button',
|
||||
text: '',
|
||||
attrs: { role: 'button' },
|
||||
rect: { left: 252, top: 74, width: 28, height: 28 },
|
||||
onClick: () => {
|
||||
menu.style.display = 'block'
|
||||
deleteItem.style.display = 'block'
|
||||
},
|
||||
})
|
||||
currentHistory = makeElement({
|
||||
tagName: 'a',
|
||||
text: '合肥全屋定制服务商选择指南',
|
||||
attrs: { href: '/chat/38432306062349826', id: 'conversation_38432306062349826' },
|
||||
rect: { left: 16, top: 64, width: 272, height: 48 },
|
||||
className: 'chat-item',
|
||||
children: [
|
||||
makeElement({
|
||||
tagName: 'span',
|
||||
text: '合肥全屋定制服务商选择指南',
|
||||
rect: { left: 24, top: 72, width: 210, height: 28 },
|
||||
}),
|
||||
makeElement({
|
||||
tagName: 'div',
|
||||
className: 'chat-item-menu-wrapper-DPvhf1',
|
||||
rect: { left: 248, top: 72, width: 32, height: 28 },
|
||||
children: [moreButton],
|
||||
}),
|
||||
],
|
||||
})
|
||||
nextHistory = makeElement({
|
||||
tagName: 'a',
|
||||
text: '合肥全屋定制公司推荐',
|
||||
attrs: { href: '/chat/38432306010828802', id: 'conversation_38432306010828802' },
|
||||
rect: { left: 16, top: 116, width: 272, height: 48 },
|
||||
className: 'chat-item',
|
||||
})
|
||||
|
||||
body.appendChild(currentHistory)
|
||||
body.appendChild(nextHistory)
|
||||
body.appendChild(menu)
|
||||
body.appendChild(dialog)
|
||||
|
||||
const documentMock = {
|
||||
body,
|
||||
querySelectorAll: (selector: string) => body.querySelectorAll(selector),
|
||||
querySelector: (selector: string) => body.querySelector(selector),
|
||||
}
|
||||
const windowMock = {
|
||||
location: {
|
||||
href: 'https://www.doubao.com/chat/',
|
||||
origin: 'https://www.doubao.com',
|
||||
},
|
||||
innerWidth: 1280,
|
||||
innerHeight: 900,
|
||||
setTimeout: (callback: () => void) => {
|
||||
callback()
|
||||
return 1
|
||||
},
|
||||
getComputedStyle: (element: MockElement) => element.style,
|
||||
}
|
||||
|
||||
try {
|
||||
const mutableGlobal = globalThis as Record<string, unknown>
|
||||
mutableGlobal.window = windowMock
|
||||
mutableGlobal.document = documentMock
|
||||
mutableGlobal.HTMLElement = MockElement
|
||||
mutableGlobal.MouseEvent = MockMouseEvent
|
||||
mutableGlobal.URL = URL
|
||||
|
||||
const result = await deleteDoubaoCurrentConversationInPage({
|
||||
conversationId: null,
|
||||
pageUrl: 'https://www.doubao.com/chat/',
|
||||
title: '合肥全屋定制服务商怎么选',
|
||||
})
|
||||
|
||||
expect(result).toMatchObject({
|
||||
ok: true,
|
||||
conversation_id: '38432306062349826',
|
||||
menu_clicked: true,
|
||||
delete_clicked: true,
|
||||
confirm_clicked: true,
|
||||
})
|
||||
expect(body.children.includes(currentHistory)).toBe(false)
|
||||
expect(body.children.includes(nextHistory)).toBe(true)
|
||||
} finally {
|
||||
const mutableGlobal = globalThis as Record<string, unknown>
|
||||
mutableGlobal.window = previousWindow
|
||||
mutableGlobal.document = previousDocument
|
||||
mutableGlobal.HTMLElement = previousHTMLElement
|
||||
mutableGlobal.MouseEvent = previousMouseEvent
|
||||
mutableGlobal.URL = previousURL
|
||||
}
|
||||
})
|
||||
|
||||
it('filters byteimg and bytednsdoc asset CDN links from source items', () => {
|
||||
expect(isNonCitationAssetDomain('p3-spider-image-sign.byteimg.com')).toBe(true)
|
||||
expect(isNonCitationAssetDomain('lf3-static.bytednsdoc.com')).toBe(true)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user