mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-09-08 09:16:58 +08:00
feat: 为词表编辑器增加可选行号与语法高亮 (#546)
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
import ace from 'ace-builds'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import '@/ace-config'
|
||||
|
||||
interface AceToken {
|
||||
type: string
|
||||
value: string
|
||||
}
|
||||
|
||||
interface WordListSyntaxMode {
|
||||
getTokenizer: () => {
|
||||
getLineTokens: (line: string, state: string) => { tokens: AceToken[] }
|
||||
}
|
||||
}
|
||||
|
||||
const WordListSyntaxMode = ace.require('ace/mode/word_list_syntax').Mode as new () => WordListSyntaxMode
|
||||
const tokenizer = new WordListSyntaxMode().getTokenizer()
|
||||
|
||||
function tokenize(line: string) {
|
||||
return tokenizer.getLineTokens(line, 'start').tokens
|
||||
}
|
||||
|
||||
describe('word list syntax mode', () => {
|
||||
it('highlights a block word as one field', () => {
|
||||
expect(tokenize('屏蔽词')).toEqual([{ type: 'word_list_block', value: '屏蔽词' }])
|
||||
})
|
||||
|
||||
it('separates replaced and replacement fields without parsing their content', () => {
|
||||
expect(tokenize('旧名.* => 新名 {[tmdbid=123;type=tv]}')).toEqual([
|
||||
{ type: 'word_list_replaced', value: '旧名.*' },
|
||||
{ type: 'keyword.operator.word-list', value: ' => ' },
|
||||
{ type: 'word_list_replacement', value: '新名 {[tmdbid=123;type=tv]}' },
|
||||
])
|
||||
})
|
||||
|
||||
it('separates front, back, and episode offset fields', () => {
|
||||
expect(tokenize('第 <> 集 >> EP+1')).toEqual([
|
||||
{ type: 'word_list_front', value: '第' },
|
||||
{ type: 'keyword.operator.word-list', value: ' <> ' },
|
||||
{ type: 'word_list_back', value: '集' },
|
||||
{ type: 'keyword.operator.word-list', value: ' >> ' },
|
||||
{ type: 'word_list_offset', value: 'EP+1' },
|
||||
])
|
||||
})
|
||||
|
||||
it('uses all six field types in combined and standalone rules', () => {
|
||||
const combinedTokens = tokenize('旧名 => 新名 && 第 <> 集 >> 2*EP-1')
|
||||
|
||||
expect(combinedTokens).toEqual([
|
||||
{ type: 'word_list_replaced', value: '旧名' },
|
||||
{ type: 'keyword.operator.word-list', value: ' => ' },
|
||||
{ type: 'word_list_replacement', value: '新名' },
|
||||
{ type: 'keyword.operator.word-list', value: ' && ' },
|
||||
{ type: 'word_list_front', value: '第' },
|
||||
{ type: 'keyword.operator.word-list', value: ' <> ' },
|
||||
{ type: 'word_list_back', value: '集' },
|
||||
{ type: 'keyword.operator.word-list', value: ' >> ' },
|
||||
{ type: 'word_list_offset', value: '2*EP-1' },
|
||||
])
|
||||
expect(tokenize('屏蔽词')[0].type).toBe('word_list_block')
|
||||
})
|
||||
})
|
||||
@@ -578,6 +578,90 @@ function registerWordListMode() {
|
||||
exports.Mode = Mode
|
||||
},
|
||||
)
|
||||
|
||||
aceModule.define?.(
|
||||
'ace/mode/word_list_syntax_highlight_rules',
|
||||
['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text_highlight_rules'],
|
||||
(require: any, exports: any) => {
|
||||
const oop = require('../lib/oop')
|
||||
const TextHighlightRules = require('./text_highlight_rules').TextHighlightRules
|
||||
|
||||
const WordListSyntaxHighlightRules = function (this: any) {
|
||||
this.$rules = {
|
||||
start: [
|
||||
{
|
||||
token: 'comment.word-list',
|
||||
regex: /^#.*/,
|
||||
},
|
||||
{
|
||||
token: [
|
||||
'text',
|
||||
'word_list_replaced',
|
||||
'keyword.operator.word-list',
|
||||
'word_list_replacement',
|
||||
'keyword.operator.word-list',
|
||||
'word_list_front',
|
||||
'keyword.operator.word-list',
|
||||
'word_list_back',
|
||||
'keyword.operator.word-list',
|
||||
'word_list_offset',
|
||||
'text',
|
||||
],
|
||||
regex: /^(\s*)(.*?)( +=> +)(.*?)( +&& +)(.*?)( +<> +)(.*?)( +>> +)(.*?)(\s*)$/,
|
||||
},
|
||||
{
|
||||
token: ['text', 'word_list_replaced', 'keyword.operator.word-list', 'word_list_replacement', 'text'],
|
||||
regex: /^(\s*)(.*?)( +=> +)(.*?)(\s*)$/,
|
||||
},
|
||||
{
|
||||
token: [
|
||||
'text',
|
||||
'word_list_front',
|
||||
'keyword.operator.word-list',
|
||||
'word_list_back',
|
||||
'keyword.operator.word-list',
|
||||
'word_list_offset',
|
||||
'text',
|
||||
],
|
||||
regex: /^(\s*)(.*?)( +<> +)(.*?)( +>> +)(.*?)(\s*)$/,
|
||||
},
|
||||
{
|
||||
token: ['text', 'word_list_block', 'text'],
|
||||
regex: /^(\s*)(\S(?:.*?\S)?)(\s*)$/,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
this.normalizeRules()
|
||||
}
|
||||
|
||||
oop.inherits(WordListSyntaxHighlightRules, TextHighlightRules)
|
||||
exports.WordListSyntaxHighlightRules = WordListSyntaxHighlightRules
|
||||
},
|
||||
)
|
||||
|
||||
aceModule.define?.(
|
||||
'ace/mode/word_list_syntax',
|
||||
['require', 'exports', 'module', 'ace/lib/oop', 'ace/mode/text', 'ace/mode/word_list_syntax_highlight_rules'],
|
||||
(require: any, exports: any) => {
|
||||
const oop = require('../lib/oop')
|
||||
const TextMode = require('./text').Mode
|
||||
const WordListSyntaxHighlightRules = require('./word_list_syntax_highlight_rules').WordListSyntaxHighlightRules
|
||||
|
||||
const Mode = function (this: any) {
|
||||
TextMode.call(this)
|
||||
this.HighlightRules = WordListSyntaxHighlightRules
|
||||
}
|
||||
|
||||
oop.inherits(Mode, TextMode)
|
||||
|
||||
;(function (this: any) {
|
||||
this.$id = 'ace/mode/word_list_syntax'
|
||||
}).call(Mode.prototype)
|
||||
|
||||
exports.Mode = Mode
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
ace.config.setModuleUrl('ace/mode/json', modeJsonUrl)
|
||||
|
||||
@@ -2215,6 +2215,8 @@ export default {
|
||||
entryCount: '{count} entries',
|
||||
ruleCount: '{count} rules',
|
||||
listLabel: 'Word list content (one rule per line)',
|
||||
lineNumbers: 'Line numbers',
|
||||
syntaxHighlighting: 'Syntax highlighting',
|
||||
saved: 'Saved',
|
||||
unsaved: 'Unsaved changes',
|
||||
saveChanges: 'Save changes',
|
||||
|
||||
@@ -2177,6 +2177,8 @@ export default {
|
||||
entryCount: '{count} 条',
|
||||
ruleCount: '共 {count} 条规则',
|
||||
listLabel: '词表内容(每行一个规则)',
|
||||
lineNumbers: '行号',
|
||||
syntaxHighlighting: '语法高亮',
|
||||
saved: '已保存',
|
||||
unsaved: '有未保存修改',
|
||||
saveChanges: '保存更改',
|
||||
|
||||
@@ -2176,6 +2176,8 @@ export default {
|
||||
entryCount: '{count} 條',
|
||||
ruleCount: '共 {count} 條規則',
|
||||
listLabel: '詞表內容(每行一個規則)',
|
||||
lineNumbers: '行號',
|
||||
syntaxHighlighting: '語法高亮',
|
||||
saved: '已保存',
|
||||
unsaved: '有未保存修改',
|
||||
saveChanges: '保存更改',
|
||||
|
||||
@@ -10,6 +10,9 @@ const { t } = useI18n()
|
||||
const $toast = useToast()
|
||||
const { global: globalTheme } = useTheme()
|
||||
|
||||
const WORDS_LINE_NUMBERS_STORAGE_KEY = 'MP_WORDS_SHOW_LINE_NUMBERS'
|
||||
const WORDS_SYNTAX_HIGHLIGHTING_STORAGE_KEY = 'MP_WORDS_SYNTAX_HIGHLIGHTING'
|
||||
|
||||
type TextSectionKey = 'identifiers' | 'releaseGroups' | 'customization' | 'excludeWords'
|
||||
type WordSectionKey = TextSectionKey | 'episodeRules'
|
||||
|
||||
@@ -45,16 +48,29 @@ const episodeFormatRules = ref<EpisodeFormatRule[]>([])
|
||||
const activeSection = ref<WordSectionKey>('identifiers')
|
||||
const expandedHelp = ref<string | null>(null)
|
||||
const saving = ref(false)
|
||||
const showLineNumbers = ref(localStorage.getItem(WORDS_LINE_NUMBERS_STORAGE_KEY) === 'true')
|
||||
const showSyntaxHighlighting = ref(localStorage.getItem(WORDS_SYNTAX_HIGHLIGHTING_STORAGE_KEY) === 'true')
|
||||
|
||||
const textEditorLanguage = computed(() => (showSyntaxHighlighting.value ? 'word_list_syntax' : 'word_list'))
|
||||
const textEditorTheme = computed(() => (globalTheme.current.value.dark ? 'github_dark' : 'github_light_default'))
|
||||
const textEditorOptions = {
|
||||
const textEditorOptions = computed(() => ({
|
||||
fontSize: 13.6,
|
||||
highlightActiveLine: false,
|
||||
scrollPastEnd: 0,
|
||||
showGutter: false,
|
||||
showFoldWidgets: false,
|
||||
showGutter: showLineNumbers.value,
|
||||
showLineNumbers: showLineNumbers.value,
|
||||
showPrintMargin: false,
|
||||
tabSize: 2,
|
||||
}
|
||||
}))
|
||||
|
||||
watch(showLineNumbers, value => {
|
||||
localStorage.setItem(WORDS_LINE_NUMBERS_STORAGE_KEY, String(value))
|
||||
})
|
||||
|
||||
watch(showSyntaxHighlighting, value => {
|
||||
localStorage.setItem(WORDS_SYNTAX_HIGHLIGHTING_STORAGE_KEY, String(value))
|
||||
})
|
||||
|
||||
const savedTextValues = reactive<Record<TextSectionKey, string>>({
|
||||
identifiers: '',
|
||||
@@ -483,13 +499,33 @@ onMounted(() => {
|
||||
<template v-if="isTextSection">
|
||||
<div class="words-field-meta">
|
||||
<strong>{{ t('setting.words.listLabel') }}</strong>
|
||||
<span>{{ t('setting.words.entryCount', { count: activeSectionCount }) }}</span>
|
||||
<div class="words-field-actions">
|
||||
<span>{{ t('setting.words.entryCount', { count: activeSectionCount }) }}</span>
|
||||
<VSwitch
|
||||
v-if="activeSection === 'identifiers'"
|
||||
v-model="showLineNumbers"
|
||||
class="words-editor-switch"
|
||||
color="primary"
|
||||
density="compact"
|
||||
hide-details
|
||||
:label="t('setting.words.lineNumbers')"
|
||||
/>
|
||||
<VSwitch
|
||||
v-if="activeSection === 'identifiers'"
|
||||
v-model="showSyntaxHighlighting"
|
||||
class="words-editor-switch"
|
||||
color="primary"
|
||||
density="compact"
|
||||
hide-details
|
||||
:label="t('setting.words.syntaxHighlighting')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<VAceEditor
|
||||
v-if="activeSection === 'identifiers'"
|
||||
v-model:value="activeTextValue"
|
||||
lang="word_list"
|
||||
:lang="textEditorLanguage"
|
||||
:theme="textEditorTheme"
|
||||
:options="textEditorOptions"
|
||||
:placeholder="activeTextPlaceholder"
|
||||
@@ -856,6 +892,23 @@ onMounted(() => {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.words-field-actions {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.words-editor-switch {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.words-editor-switch :deep(.v-label) {
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.words-textarea :deep(textarea) {
|
||||
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace;
|
||||
font-size: 0.85rem;
|
||||
@@ -867,6 +920,15 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.words-text-editor {
|
||||
--words-token-block: #af00db;
|
||||
--words-token-replaced: #001080;
|
||||
--words-token-replacement: #a31515;
|
||||
--words-token-front: #267f99;
|
||||
--words-token-back: #795e26;
|
||||
--words-token-offset: #098658;
|
||||
--words-token-comment: #008000;
|
||||
--words-token-operator: #000;
|
||||
|
||||
overflow: hidden;
|
||||
block-size: 15.8rem;
|
||||
border: 1px solid rgba(var(--v-theme-on-surface), var(--v-border-opacity));
|
||||
@@ -876,6 +938,17 @@ onMounted(() => {
|
||||
transform: translateZ(0);
|
||||
}
|
||||
|
||||
.words-text-editor.ace-github-dark {
|
||||
--words-token-block: #c586c0;
|
||||
--words-token-replaced: #9cdcfe;
|
||||
--words-token-replacement: #ce9178;
|
||||
--words-token-front: #4ec9b0;
|
||||
--words-token-back: #dcdcaa;
|
||||
--words-token-offset: #b5cea8;
|
||||
--words-token-comment: #6a9955;
|
||||
--words-token-operator: #d4d4d4;
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_scroller),
|
||||
.words-text-editor :deep(.ace_content),
|
||||
.words-text-editor :deep(.ace_text-layer) {
|
||||
@@ -884,10 +957,46 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_comment) {
|
||||
color: rgb(var(--v-theme-success)) !important;
|
||||
color: var(--words-token-comment) !important;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_gutter-layer) {
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_gutter-cell) {
|
||||
padding-inline: 0.35rem 0.25rem;
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_word_list_block) {
|
||||
color: var(--words-token-block);
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_word_list_replaced) {
|
||||
color: var(--words-token-replaced);
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_word_list_replacement) {
|
||||
color: var(--words-token-replacement);
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_word_list_front) {
|
||||
color: var(--words-token-front);
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_word_list_back) {
|
||||
color: var(--words-token-back);
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_word_list_offset) {
|
||||
color: var(--words-token-offset);
|
||||
}
|
||||
|
||||
.words-text-editor :deep(.ace_keyword.ace_operator.ace_word-list) {
|
||||
color: var(--words-token-operator);
|
||||
}
|
||||
|
||||
.words-inline-hint {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
@@ -1124,9 +1233,21 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.words-field-meta {
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.75rem;
|
||||
min-block-size: 2.75rem;
|
||||
}
|
||||
|
||||
.words-field-meta strong {
|
||||
min-inline-size: 0;
|
||||
}
|
||||
|
||||
.words-field-actions {
|
||||
gap: 0.5rem;
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
.words-textarea :deep(.v-field__input) {
|
||||
min-block-size: 18rem;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import WordsView from '@/views/system/WordsView.vue'
|
||||
import { screen, waitFor } from '@testing-library/vue'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { renderWithProviders } from '@tests/support/render'
|
||||
import { defineComponent } from 'vue'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
apiGet: vi.fn(),
|
||||
apiPost: vi.fn(),
|
||||
toastError: vi.fn(),
|
||||
toastSuccess: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/api', () => ({
|
||||
default: {
|
||||
get: mocks.apiGet,
|
||||
post: mocks.apiPost,
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock('vue-toastification', () => ({
|
||||
useToast: () => ({
|
||||
error: mocks.toastError,
|
||||
success: mocks.toastSuccess,
|
||||
}),
|
||||
}))
|
||||
|
||||
const AceEditorStub = defineComponent({
|
||||
name: 'VAceEditor',
|
||||
props: {
|
||||
lang: { type: String, default: 'text' },
|
||||
options: { type: Object, default: () => ({}) },
|
||||
value: { type: String, default: '' },
|
||||
},
|
||||
template: `
|
||||
<div
|
||||
data-testid="words-ace-editor"
|
||||
:data-lang="lang"
|
||||
:data-show-gutter="String(Boolean(options.showGutter))"
|
||||
:data-show-line-numbers="String(Boolean(options.showLineNumbers))"
|
||||
:data-value="value"
|
||||
/>
|
||||
`,
|
||||
})
|
||||
|
||||
async function renderWordsView() {
|
||||
return renderWithProviders(WordsView, {
|
||||
global: {
|
||||
stubs: {
|
||||
VAceEditor: AceEditorStub,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
describe('WordsView editor preferences', () => {
|
||||
beforeEach(() => {
|
||||
mocks.apiGet.mockImplementation((endpoint: string) => {
|
||||
if (endpoint.includes('EpisodeFormatRuleTable')) return Promise.resolve({ data: { value: [] } })
|
||||
return Promise.resolve({ data: { value: ['alpha', 'beta'] } })
|
||||
})
|
||||
mocks.apiPost.mockResolvedValue({ success: true })
|
||||
})
|
||||
|
||||
it('keeps line numbers disabled when no preference is stored', async () => {
|
||||
await renderWordsView()
|
||||
|
||||
const editor = await screen.findByTestId('words-ace-editor')
|
||||
const switchControl = screen.getByRole('checkbox', { name: '行号' })
|
||||
|
||||
expect(switchControl).not.toBeChecked()
|
||||
expect(editor).toHaveAttribute('data-show-gutter', 'false')
|
||||
expect(editor).toHaveAttribute('data-show-line-numbers', 'false')
|
||||
expect(editor).toHaveAttribute('data-lang', 'word_list')
|
||||
expect(screen.getByRole('checkbox', { name: '语法高亮' })).not.toBeChecked()
|
||||
expect(localStorage.getItem('MP_WORDS_SHOW_LINE_NUMBERS')).toBeNull()
|
||||
expect(localStorage.getItem('MP_WORDS_SYNTAX_HIGHLIGHTING')).toBeNull()
|
||||
})
|
||||
|
||||
it('restores the enabled preference from local storage', async () => {
|
||||
localStorage.setItem('MP_WORDS_SHOW_LINE_NUMBERS', 'true')
|
||||
|
||||
await renderWordsView()
|
||||
|
||||
const editor = await screen.findByTestId('words-ace-editor')
|
||||
|
||||
expect(screen.getByRole('checkbox', { name: '行号' })).toBeChecked()
|
||||
expect(editor).toHaveAttribute('data-show-gutter', 'true')
|
||||
expect(editor).toHaveAttribute('data-show-line-numbers', 'true')
|
||||
})
|
||||
|
||||
it('treats an unrecognized stored preference as disabled', async () => {
|
||||
localStorage.setItem('MP_WORDS_SHOW_LINE_NUMBERS', 'enabled')
|
||||
|
||||
await renderWordsView()
|
||||
|
||||
const editor = await screen.findByTestId('words-ace-editor')
|
||||
|
||||
expect(screen.getByRole('checkbox', { name: '行号' })).not.toBeChecked()
|
||||
expect(editor).toHaveAttribute('data-show-gutter', 'false')
|
||||
expect(editor).toHaveAttribute('data-show-line-numbers', 'false')
|
||||
})
|
||||
|
||||
it('restores the syntax highlighting preference from local storage', async () => {
|
||||
localStorage.setItem('MP_WORDS_SYNTAX_HIGHLIGHTING', 'true')
|
||||
|
||||
await renderWordsView()
|
||||
|
||||
const editor = await screen.findByTestId('words-ace-editor')
|
||||
|
||||
expect(screen.getByRole('checkbox', { name: '语法高亮' })).toBeChecked()
|
||||
expect(editor).toHaveAttribute('data-lang', 'word_list_syntax')
|
||||
})
|
||||
|
||||
it('updates Ace options and persists the preference without changing content', async () => {
|
||||
const user = userEvent.setup()
|
||||
await renderWordsView()
|
||||
|
||||
const editor = await screen.findByTestId('words-ace-editor')
|
||||
await waitFor(() => expect(editor).toHaveAttribute('data-value', 'alpha\nbeta'))
|
||||
|
||||
await user.click(screen.getByRole('checkbox', { name: '行号' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(editor).toHaveAttribute('data-show-gutter', 'true')
|
||||
expect(editor).toHaveAttribute('data-show-line-numbers', 'true')
|
||||
expect(localStorage.getItem('MP_WORDS_SHOW_LINE_NUMBERS')).toBe('true')
|
||||
})
|
||||
expect(editor).toHaveAttribute('data-value', 'alpha\nbeta')
|
||||
expect(mocks.apiPost).not.toHaveBeenCalled()
|
||||
|
||||
await user.click(screen.getByRole('checkbox', { name: '行号' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(editor).toHaveAttribute('data-show-gutter', 'false')
|
||||
expect(editor).toHaveAttribute('data-show-line-numbers', 'false')
|
||||
expect(localStorage.getItem('MP_WORDS_SHOW_LINE_NUMBERS')).toBe('false')
|
||||
})
|
||||
})
|
||||
|
||||
it('switches the Ace mode and persists syntax highlighting without changing content', async () => {
|
||||
const user = userEvent.setup()
|
||||
await renderWordsView()
|
||||
|
||||
const editor = await screen.findByTestId('words-ace-editor')
|
||||
await waitFor(() => expect(editor).toHaveAttribute('data-value', 'alpha\nbeta'))
|
||||
|
||||
await user.click(screen.getByRole('checkbox', { name: '语法高亮' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(editor).toHaveAttribute('data-lang', 'word_list_syntax')
|
||||
expect(localStorage.getItem('MP_WORDS_SYNTAX_HIGHLIGHTING')).toBe('true')
|
||||
})
|
||||
expect(editor).toHaveAttribute('data-value', 'alpha\nbeta')
|
||||
expect(mocks.apiPost).not.toHaveBeenCalled()
|
||||
|
||||
await user.click(screen.getByRole('checkbox', { name: '语法高亮' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(editor).toHaveAttribute('data-lang', 'word_list')
|
||||
expect(localStorage.getItem('MP_WORDS_SYNTAX_HIGHLIGHTING')).toBe('false')
|
||||
})
|
||||
})
|
||||
|
||||
it('shows the switch only for custom identifiers', async () => {
|
||||
const user = userEvent.setup()
|
||||
await renderWordsView()
|
||||
await screen.findByTestId('words-ace-editor')
|
||||
|
||||
const releaseGroupButtons = screen.getAllByRole('button', { name: /自定义制作组\/字幕组/ })
|
||||
await user.click(releaseGroupButtons[0])
|
||||
|
||||
expect(screen.queryByRole('checkbox', { name: '行号' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('checkbox', { name: '语法高亮' })).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('words-ace-editor')).not.toBeInTheDocument()
|
||||
expect(mocks.apiPost).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user