feat: add generate lesson dialog

This commit is contained in:
2026-06-15 20:14:07 -06:00
parent 7daa0e8250
commit 74687c1de1
2 changed files with 94 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
import { mount } from '@vue/test-utils'
import { describe, expect, it } from 'vitest'
import GenerateLessonDialog from './GenerateLessonDialog.vue'
describe('GenerateLessonDialog', () => {
it('disables submit until a topic is entered', async () => {
const wrapper = mount(GenerateLessonDialog, { props: { loading: false, error: null } })
const submit = wrapper.findAll('button')[0]!
expect(submit.attributes('disabled')).toBeDefined()
await wrapper.get('input').setValue('CSS 弹性布局')
expect(submit.attributes('disabled')).toBeUndefined()
})
it('emits submit with the trimmed topic', async () => {
const wrapper = mount(GenerateLessonDialog, { props: { loading: false, error: null } })
await wrapper.get('input').setValue(' CSS 弹性布局 ')
await wrapper.findAll('button')[0]!.trigger('click')
expect(wrapper.emitted('submit')).toEqual([['CSS 弹性布局']])
})
it('shows a loading state and disables interaction', () => {
const wrapper = mount(GenerateLessonDialog, { props: { loading: true, error: null } })
expect(wrapper.get('input').attributes('disabled')).toBeDefined()
expect(wrapper.findAll('button')[0]!.text()).toContain('生成中')
expect(wrapper.findAll('button')[0]!.attributes('disabled')).toBeDefined()
})
it('shows an error message and allows retry without closing', async () => {
const wrapper = mount(GenerateLessonDialog, { props: { loading: false, error: 'Deepseek 请求失败。' } })
expect(wrapper.text()).toContain('Deepseek 请求失败。')
expect(wrapper.findAll('button')[0]!.attributes('disabled')).toBeDefined()
await wrapper.get('input').setValue('CSS 弹性布局')
expect(wrapper.findAll('button')[0]!.attributes('disabled')).toBeUndefined()
})
it('emits cancel', async () => {
const wrapper = mount(GenerateLessonDialog, { props: { loading: false, error: null } })
await wrapper.findAll('button')[1]!.trigger('click')
expect(wrapper.emitted('cancel')).toHaveLength(1)
})
})

View File

@@ -0,0 +1,44 @@
<script setup lang="ts">
import { ref } from 'vue'
const props = defineProps<{
loading: boolean
error: string | null
}>()
const emit = defineEmits<{
submit: [topic: string]
cancel: []
}>()
const topic = ref('')
function submit(): void {
const value = topic.value.trim()
if (!value || props.loading) return
emit('submit', value)
}
</script>
<template>
<div class="dialog-overlay" role="dialog" aria-modal="true" aria-labelledby="generate-lesson-title">
<div class="dialog">
<h2 id="generate-lesson-title">生成教案</h2>
<p>输入主题AI 将生成一份符合模板结构的教案加入当前整本末尾</p>
<input
v-model="topic"
type="text"
placeholder="例如CSS 弹性布局入门"
:disabled="loading"
@keydown.enter="submit"
/>
<p v-if="error" class="app-notice app-notice--error" role="alert">{{ error }}</p>
<div class="dialog-actions">
<button type="button" :disabled="loading || !topic.trim()" @click="submit">
{{ loading ? '生成中' : '生成' }}
</button>
<button type="button" :disabled="loading" @click="emit('cancel')">取消</button>
</div>
</div>
</div>
</template>