test: fixed course ui tests based on new flow

This commit is contained in:
Jannat Patel
2026-01-29 16:17:34 +05:30
parent 5970540a99
commit 7fe398fc66
5 changed files with 184 additions and 139 deletions

View File

@@ -11,7 +11,6 @@ describe("Course Creation", () => {
cy.get("button").contains("Create").click(); cy.get("button").contains("Create").click();
cy.get("span").contains("New Course").click(); cy.get("span").contains("New Course").click();
cy.wait(500); cy.wait(500);
cy.url().should("include", "/courses/new/edit");
cy.get("label").contains("Title").type("Test Course"); cy.get("label").contains("Title").type("Test Course");
cy.get("label") cy.get("label")
@@ -35,21 +34,6 @@ describe("Course Creation", () => {
}); });
}); });
cy.get("label")
.contains("Preview Video")
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
cy.get("label")
.contains("Category")
.parent()
.within(() => {
cy.get("button").click();
});
cy.get("[id^=headlessui-combobox-option-")
.should("be.visible")
.first()
.click();
/* Instructor */ /* Instructor */
cy.get("label") cy.get("label")
.contains("Instructors") .contains("Instructors")
@@ -69,13 +53,32 @@ describe("Course Creation", () => {
}); });
}); });
cy.button("Create").last().click();
// Edit Course Details
cy.wait(500);
cy.get("label")
.contains("Preview Video")
.type("https://www.youtube.com/embed/-LPmw2Znl2c");
cy.get("[id=tags]").type("Learning{enter}Frappe{enter}ERPNext{enter}");
cy.get("label")
.contains("Category")
.parent()
.within(() => {
cy.get("button").click();
});
cy.get("[id^=headlessui-combobox-option-")
.should("be.visible")
.first()
.click();
cy.get("label").contains("Published").click(); cy.get("label").contains("Published").click();
cy.get("label").contains("Published On").type("2021-01-01"); cy.get("label").contains("Published On").type("2021-01-01");
cy.button("Save").click(); cy.button("Save").click();
// Add Chapter // Add Chapter
cy.wait(1000); cy.wait(1000);
cy.button("Add Chapter").click(); cy.button("Add").click();
cy.wait(1000); cy.wait(1000);
cy.get("[data-dismissable-layer]") cy.get("[data-dismissable-layer]")

View File

@@ -54,7 +54,6 @@ declare module 'vue' {
CourseCardOverlay: typeof import('./src/components/CourseCardOverlay.vue')['default'] CourseCardOverlay: typeof import('./src/components/CourseCardOverlay.vue')['default']
CourseInstructors: typeof import('./src/components/CourseInstructors.vue')['default'] CourseInstructors: typeof import('./src/components/CourseInstructors.vue')['default']
CourseOutline: typeof import('./src/components/CourseOutline.vue')['default'] CourseOutline: typeof import('./src/components/CourseOutline.vue')['default']
CourseProgressSummary: typeof import('./src/components/Modals/CourseProgressSummary.vue')['default']
CourseReviews: typeof import('./src/components/CourseReviews.vue')['default'] CourseReviews: typeof import('./src/components/CourseReviews.vue')['default']
CreateOutline: typeof import('./src/components/CreateOutline.vue')['default'] CreateOutline: typeof import('./src/components/CreateOutline.vue')['default']
DateRange: typeof import('./src/components/Common/DateRange.vue')['default'] DateRange: typeof import('./src/components/Common/DateRange.vue')['default']

View File

@@ -20,7 +20,7 @@
<NumberChartGraph :title="__('Lessons')" :value="course.data?.lessons" /> <NumberChartGraph :title="__('Lessons')" :value="course.data?.lessons" />
</div> </div>
<div class="grid grid-cols-[2fr_1fr] gap-5 items-start"> <div class="grid grid-cols-[2fr_1fr] gap-5 items-start">
<div class="border rounded-lg py-3 px-4"> <div v-if="course.data?.enrollments" class="border rounded-lg py-3 px-4">
<div class="flex items-center justify-between mb-3"> <div class="flex items-center justify-between mb-3">
<div class="text-lg font-semibold"> <div class="text-lg font-semibold">
{{ __('Students') }} {{ __('Students') }}
@@ -120,7 +120,7 @@
</div> </div>
</div> </div>
<div class="space-y-5"> <div class="space-y-5">
<div class="border rounded-lg p-4"> <div v-if="chartDetails.data?.length" class="border rounded-lg p-4">
<div class="text-ink-gray-5 mb-4"> <div class="text-ink-gray-5 mb-4">
{{ __('Progress Summary') }} {{ __('Progress Summary') }}
</div> </div>

View File

@@ -1,82 +1,82 @@
<template> <template>
<Dialog <Dialog
v-model="show" v-model="show"
:options="{ :options="{
title: __('Create Course'), title: __('Create Course'),
size: '3xl', size: '3xl',
}" }"
> >
<template #body-content> <template #body-content>
<div class="text-base"> <div class="text-base">
<div class="grid grid-cols-2 gap-5 border-b mb-5"> <div class="grid grid-cols-2 gap-5 border-b mb-5">
<FormControl <FormControl
v-model="course.title" v-model="course.title"
:label="__('Title')" :label="__('Title')"
:required="true" :required="true"
/> />
<Link <Link
doctype="LMS Category" doctype="LMS Category"
v-model="course.category" v-model="course.category"
:label="__('Category')" :label="__('Category')"
:allowCreate="true" :allowCreate="true"
@create=" @create="
() => { () => {
openSettings('Categories') openSettings('Categories')
show = false show = false
} }
" "
/> />
<MultiSelect <MultiSelect
v-model="course.instructors" v-model="course.instructors"
doctype="User" doctype="User"
:label="__('Instructors')" :label="__('Instructors')"
:filters="{ ignore_user_type: 1 }" :filters="{ ignore_user_type: 1 }"
:onCreate="(close: () => void) => openSettings('Members', close)" :onCreate="(close: () => void) => openSettings('Members', close)"
:required="true" :required="true"
/> />
<Uploader <Uploader
v-model="course.image" v-model="course.image"
:label="__('Course Image')" :label="__('Course Image')"
:required="false" :required="false"
/> />
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
<FormControl <FormControl
v-model="course.short_introduction" v-model="course.short_introduction"
:label="__('Short Introduction')" :label="__('Short Introduction')"
type="textarea" type="textarea"
:required="true" :required="true"
:rows="4" :rows="4"
/> />
<div class=""> <div class="">
<div class="mb-1.5 text-sm text-ink-gray-5"> <div class="mb-1.5 text-sm text-ink-gray-5">
{{ __('Course Description') }} {{ __('Course Description') }}
<span class="text-ink-red-3">*</span> <span class="text-ink-red-3">*</span>
</div> </div>
<TextEditor <TextEditor
:content="course.description" :content="course.description"
@change="(val: string) => (course.description = val)" @change="(val: string) => (course.description = val)"
:editable="true" :editable="true"
:fixedMenu="true" :fixedMenu="true"
editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[10rem]" editorClass="prose-sm max-w-none border-b border-x bg-surface-gray-2 rounded-b-md py-1 px-2 min-h-[10rem]"
/> />
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<template #actions="{ close }"> <template #actions="{ close }">
<div class="text-right"> <div class="text-right">
<Button variant="solid" @click="saveCourse(close)"> <Button variant="solid" @click="saveCourse(close)">
{{ __('Create') }} {{ __('Create') }}
</Button> </Button>
</div> </div>
</template> </template>
</Dialog> </Dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Button, Dialog, FormControl, TextEditor, toast } from 'frappe-ui' import { Button, Dialog, FormControl, TextEditor, toast } from 'frappe-ui'
import { Link, useOnboarding, useTelemetry } from "frappe-ui/frappe" import { Link, useOnboarding, useTelemetry } from 'frappe-ui/frappe'
import { onMounted, onBeforeUnmount, ref, watch } from 'vue'; import { inject, onMounted, onBeforeUnmount, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { openSettings } from '@/utils' import { openSettings } from '@/utils'
import MultiSelect from '@/components/Controls/MultiSelect.vue' import MultiSelect from '@/components/Controls/MultiSelect.vue'
@@ -86,58 +86,64 @@ const show = defineModel<boolean>({ required: true, default: false })
const router = useRouter() const router = useRouter()
const { capture } = useTelemetry() const { capture } = useTelemetry()
const { updateOnboardingStep } = useOnboarding('learning') const { updateOnboardingStep } = useOnboarding('learning')
const user = inject<any>('$user')
const props = defineProps<{ const props = defineProps<{
courses: any courses: any
}>() }>()
const course = ref({ const course = ref({
title: '', title: '',
short_introduction: '', short_introduction: '',
description: '', description: '',
instructors: [], instructors: [],
category: null, category: null,
image: null, image: null,
}) })
const saveCourse = (close: () => void = () => {}) => { const saveCourse = (close: () => void = () => {}) => {
props.courses.insert.submit({ props.courses.insert.submit(
...course.value, {
instructors: course.value.instructors.map((instructor) => ({ ...course.value,
instructor: instructor, instructors: course.value.instructors.map((instructor) => ({
})), instructor: instructor,
}, { })),
onSuccess(data: any) { },
toast.success(__('Course created successfully')) {
close() onSuccess(data: any) {
capture('course_created') toast.success(__('Course created successfully'))
updateOnboardingStep('create_first_course', true, false, () => { close()
localStorage.setItem('firstCourse', data.name) capture('course_created')
}) router.push({
router.push({ name: 'CourseDetail',
name: 'CourseDetail', params: { courseName: data.name },
params: { courseName: data.name }, hash: '#settings',
hash: '#settings', })
}) if (user.data?.is_system_manager) {
}, updateOnboardingStep('create_first_course', true, false, () => {
}) localStorage.setItem('firstCourse', data.name)
})
}
},
}
)
} }
const keyboardShortcut = (e: KeyboardEvent) => { const keyboardShortcut = (e: KeyboardEvent) => {
if ( if (
e.key === 's' && e.key === 's' &&
(e.ctrlKey || e.metaKey) && (e.ctrlKey || e.metaKey) &&
e.target && e.target &&
e.target instanceof HTMLElement && e.target instanceof HTMLElement &&
!e.target.classList.contains('ProseMirror') !e.target.classList.contains('ProseMirror')
) { ) {
saveCourse() saveCourse()
e.preventDefault() e.preventDefault()
} }
} }
onMounted(() => { onMounted(() => {
window.addEventListener('keydown', keyboardShortcut) window.addEventListener('keydown', keyboardShortcut)
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {

View File

@@ -38,10 +38,11 @@
"fieldtype": "Column Break" "fieldtype": "Column Break"
} }
], ],
"grid_page_length": 50,
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"links": [], "links": [],
"modified": "2023-12-21 15:25:16.744558", "modified": "2026-01-29 16:10:47.787285",
"modified_by": "Administrator", "modified_by": "sayali@frappe.io",
"module": "LMS", "module": "LMS",
"name": "LMS Course Review", "name": "LMS Course Review",
"owner": "Administrator", "owner": "Administrator",
@@ -60,7 +61,6 @@
}, },
{ {
"create": 1, "create": 1,
"delete": 1,
"email": 1, "email": 1,
"export": 1, "export": 1,
"print": 1, "print": 1,
@@ -69,8 +69,45 @@
"role": "LMS Student", "role": "LMS Student",
"share": 1, "share": 1,
"write": 1 "write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Moderator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Course Creator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Batch Evaluator",
"share": 1,
"write": 1
} }
], ],
"row_format": "Dynamic",
"search_fields": "course", "search_fields": "course",
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",