83 lines
4.8 KiB
TypeScript
83 lines
4.8 KiB
TypeScript
import { readFileSync } from 'node:fs'
|
|
import { describe, expect, it } from 'vitest'
|
|
|
|
const app = readFileSync('src/App.vue', 'utf8')
|
|
const css = readFileSync('src/memo.css', 'utf8')
|
|
const panel = readFileSync('src/MemoPanel.vue', 'utf8')
|
|
const editor = readFileSync('src/components/MemoEditor.vue', 'utf8')
|
|
|
|
describe('memo shell integration', () => {
|
|
it('places Memos immediately after Countdowns in desktop navigation and exposes it as a direct mobile destination', () => {
|
|
const nav = app.slice(app.indexOf('<nav class="primary-nav">'), app.indexOf('</nav>', app.indexOf('<nav class="primary-nav">')))
|
|
expect(nav.indexOf("switchView('memos')")).toBeGreaterThan(nav.indexOf("switchView('countdowns')"))
|
|
expect(nav.match(/switchView\('memos'\)/g)).toHaveLength(1)
|
|
const bottom = app.slice(app.indexOf('<nav class="bottom"'), app.indexOf('</nav>', app.indexOf('<nav class="bottom"')))
|
|
expect(bottom).toContain("switchView('memos')")
|
|
expect(bottom.match(/aria-current=/g)).toHaveLength(5)
|
|
})
|
|
|
|
it('routes the shared cat FAB to a local memo draft, hides it in trash, and defers POST until save', () => {
|
|
expect(app).toContain("activeView.value === 'memos'")
|
|
expect(app).toContain('memoPanel.value?.createMemo()')
|
|
expect(app).toContain(':show="showFloatingAdd"')
|
|
expect(panel).toContain("scope.value === 'trash'")
|
|
const createBlock = panel.slice(panel.indexOf('async function createMemo()'), panel.indexOf('function memoExcerpt'))
|
|
expect(createBlock).toContain('id: null')
|
|
expect(createBlock).not.toContain("props.request('/memos'")
|
|
expect(editor).toContain("memoId === null ? '/memos'")
|
|
})
|
|
|
|
it('keeps memo UI componentized and task detail copy explicit', () => {
|
|
expect(app).toContain("import MemoPanel from './MemoPanel.vue'")
|
|
expect(panel).toContain("import MemoRow")
|
|
expect(panel).toContain("import MemoEditor")
|
|
expect(app).toContain('<span id="task-detail-title">任务详情</span>')
|
|
expect(app).toContain('<div class="field-label"><span>任务备注</span>')
|
|
})
|
|
|
|
it('adds a compact, reusable Markdown editor and preview to memo details', () => {
|
|
expect(editor).toContain("import { applyMarkdownFormat, renderMarkdown")
|
|
expect(editor).toContain('aria-label="备忘录 Markdown 格式"')
|
|
expect(editor).toContain('aria-label="备忘录 Markdown 预览"')
|
|
expect(editor).toContain('@keydown="handleMemoBodyShortcut"')
|
|
expect(editor).toContain('v-if="!memoPreview && !memo.deleted_at"')
|
|
expect(css).toContain('.memo-markdown-field{display:grid;gap:7px')
|
|
expect(css).toContain('.memo-markdown-editor .markdown-toolbar{')
|
|
expect(css).toContain('.memo-markdown-preview{min-height:250px')
|
|
})
|
|
|
|
it('uses AppSheet for mobile memo detail while keeping desktop detail non-modal', () => {
|
|
expect(panel).toContain("import AppSheet from './components/AppSheet.vue'")
|
|
expect(panel).toContain('<AppSheet :open="Boolean(selected)" :modal="mobileDetail"')
|
|
expect(panel).toContain('panel-class="memo-editor"')
|
|
expect(panel).toContain('title-id="memo-editor-title"')
|
|
expect(panel).not.toContain('memo-editor-scrim')
|
|
expect(editor).not.toContain('aria-modal')
|
|
expect(editor).not.toContain("event.key !== 'Tab'")
|
|
})
|
|
|
|
it('tracks editor state in the shell, reserves desktop space, hides the FAB, and marks mobile background regions inert', () => {
|
|
expect(app).toContain('const memoDetailOpen = ref(false)')
|
|
expect(app).toContain("'memo-detail-open': activeView==='memos' && memoDetailOpen")
|
|
expect(app).toContain('@detail="memoDetailOpen=$event"')
|
|
expect(app).toContain(':inert="memoBackgroundInert ? true : undefined"')
|
|
expect(app).toContain(':show="showFloatingAdd"')
|
|
expect(css).toContain('.shell.memo-detail-open main{padding-right:372px}')
|
|
expect(css).toContain('.memo-editor__fields{flex:1;min-height:0;overflow-y:auto;overflow-x:hidden')
|
|
expect(css).toContain('@media(max-width:930px){.shell.memo-detail-open main{padding:20px 17px 112px}')
|
|
expect(css).toContain('.memo-editor{position:fixed;z-index:42;left:0;right:0;top:auto;bottom:0;width:100%;max-width:100%')
|
|
})
|
|
|
|
it('uses a 350px desktop detail, near-full mobile sheet, continuous rows and reduced motion', () => {
|
|
expect(css).toContain('.memo-editor{width:350px')
|
|
expect(css).toContain('.memo-list{display:grid;gap:0')
|
|
expect(css).toContain('.memo-row+.memo-row{border-top:1px solid var(--border-cream)}')
|
|
const mobileMedia = css.slice(css.indexOf('@media(max-width:930px){'), css.indexOf('@media(prefers-reduced-motion:reduce)'))
|
|
expect(mobileMedia).toContain('.memo-panel{')
|
|
expect(css).toContain('.memo-editor{position:fixed;z-index:42;left:0;right:0;top:auto')
|
|
expect(css).toContain('height:min(92dvh,820px)')
|
|
expect(css).toContain('@media(prefers-reduced-motion:reduce){.memo-editor')
|
|
expect(editor).toContain("window.addEventListener('beforeunload'")
|
|
})
|
|
})
|