1- import { describe , it , expect , beforeEach , vi } from "vitest" ;
1+ import { describe , it , expect , vi , beforeEach } from "vitest" ;
22import { mount } from "@vue/test-utils" ;
33import NewModel from "@/components/models/NewModel.vue" ;
4- import SvgIcon from '@/components/shared/SvgIcon.vue' ;
54import ElementPlus from 'element-plus'
6- import * as ElementPlusIconsVue from '@element-plus/icons-vue'
5+ import waitForExpect from 'wait-for-expect' ;
76
8- const delay = ( ms ) => new Promise ( resolve => setTimeout ( resolve , ms ) ) ;
7+ // Mock vue-i18n
8+ vi . mock ( 'vue-i18n' , ( ) => ( {
9+ useI18n : ( ) => ( {
10+ t : ( key ) => key ,
11+ } ) ,
12+ } ) ) ;
913
14+ // Mock UserStore
15+ vi . mock ( '../../../stores/UserStore' , ( ) => ( {
16+ default : ( ) => ( {
17+ username : 'testuser' ,
18+ orgs : [ { path : 'testorg' } ]
19+ } )
20+ } ) ) ;
21+
22+ // Mock useFetchApi to handle license fetching and form submission
23+ vi . mock ( '../../../packs/useFetchApi' , ( ) => {
24+ const mock = vi . fn ( ) . mockImplementation ( ( url ) => {
25+ if ( url . startsWith ( '/tags' ) ) {
26+ return {
27+ json : ( ) => Promise . resolve ( {
28+ data : { value : { data : [ { name : 'MIT' } , { name : 'Apache-2.0' } ] } }
29+ } )
30+ } ;
31+ }
32+ if ( url === '/models' ) {
33+ return {
34+ post : ( ) => ( {
35+ json : ( ) => Promise . resolve ( {
36+ data : { value : { data : { path : 'testuser/testmodel' } } } ,
37+ error : { value : null }
38+ } )
39+ } )
40+ } ;
41+ }
42+ // Default mock for any other calls
43+ return {
44+ post : ( ) => ( {
45+ json : ( ) => Promise . resolve ( { data : { value : { } } , error : { value : null } } )
46+ } ) ,
47+ json : ( ) => Promise . resolve ( { data : { value : { } } } )
48+ } ;
49+ } ) ;
50+ return { default : mock } ;
51+ } ) ;
1052
1153const createWrapper = ( props ) => {
1254 return mount ( NewModel , {
1355 global : {
56+ plugins : [ ElementPlus ] ,
1457 provide : {
1558 nameRule : / ^ [ a - z A - Z ] [ a - z A - Z 0 - 9 - _ .] * [ a - z A - Z 0 - 9 ] $ / ,
59+ } ,
60+ stubs : {
61+ SvgIcon : true ,
62+ PublicAndPrivateRadioGroup : true ,
63+ // Stub CsgButton to be a simple button that emits a click event
64+ CsgButton : {
65+ template : '<button @click="$emit(\'click\')"><slot/></button>' ,
66+ props : [ 'loading' ]
67+ }
1668 }
1769 } ,
1870 props : {
@@ -22,67 +74,94 @@ const createWrapper = (props) => {
2274 } ) ;
2375} ;
2476
25-
77+ // Helper to trigger form submission and wait for reactivity
2678async function triggerFormButton ( wrapper ) {
27- const button = wrapper . findComponent ( { name : 'CsgButton' } )
79+ const button = wrapper . find ( 'button' ) ;
2880 await button . trigger ( 'click' ) ;
29- await delay ( 300 ) ;
30- await wrapper . vm . $nextTick ( )
81+ // Wait for the next DOM update cycle
82+ await wrapper . vm . $nextTick ( ) ;
3183}
3284
33- // Mock stores
34- vi . mock ( '../../../stores/UserStore' , ( ) => ( {
35- default : ( ) => ( {
36- username : 'testuser' ,
37- orgs : [ { path : 'testorg' } ]
38- } )
39- } ) ) ;
40-
41- const buttonClass = '.btn.btn-primary'
42-
4385describe ( "NewModel" , ( ) => {
44- describe ( "mount" , async ( ) => {
45- it ( "mounts correctly" , ( ) => {
86+ beforeEach ( ( ) => {
87+ // Reset mocks and window.location before each test
88+ vi . clearAllMocks ( ) ;
89+ delete window . location ;
90+ window . location = { href : '' } ;
91+ } ) ;
92+
93+ describe ( "mount" , ( ) => {
94+ it ( "mounts correctly" , async ( ) => {
4695 const wrapper = createWrapper ( ) ;
96+ // Wait for async operations in onMounted to complete
97+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
4798 expect ( wrapper . exists ( ) ) . toBe ( true ) ;
4899 } ) ;
49100 } ) ;
50101
51102 describe ( "form validation" , ( ) => {
52103 it ( "validates required fields" , async ( ) => {
53104 const wrapper = createWrapper ( ) ;
105+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ; // Wait for mount
106+ // Clear pre-filled fields to test required rule
107+ wrapper . vm . dataForm . owner = '' ;
108+ wrapper . vm . dataForm . name = '' ;
109+ wrapper . vm . dataForm . license = '' ;
54110 await triggerFormButton ( wrapper ) ;
55- const formErrors = wrapper . findAll ( '.el-form-item__error' ) ;
56- expect ( formErrors . length ) . toBeGreaterThan ( 0 ) ;
111+
112+ await waitForExpect ( ( ) => {
113+ const formErrors = wrapper . findAll ( '.el-form-item__error' ) ;
114+ expect ( formErrors . length ) . toBe ( 3 ) ; // owner, name, license
115+ } ) ;
57116 } ) ;
58117
59118 it ( "validates invalid model name" , async ( ) => {
60119 const wrapper = createWrapper ( ) ;
120+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
61121 wrapper . vm . dataForm . name = 'a' ; // Invalid length
122+ wrapper . vm . dataForm . license = 'MIT' ; // Provide valid license
62123 await triggerFormButton ( wrapper ) ;
63- expect ( wrapper . find ( '.el-form-item__error' ) . exists ( ) ) . toBe ( true ) ;
124+
125+ await waitForExpect ( ( ) => {
126+ const error = wrapper . find ( '.el-form-item__error' ) ;
127+ expect ( error . exists ( ) ) . toBe ( true ) ;
128+ expect ( error . text ( ) ) . toContain ( 'rule.lengthLimit' ) ;
129+ } ) ;
64130 } ) ;
65131
66- it ( "validates valid model name " , async ( ) => {
132+ it ( "validates valid form " , async ( ) => {
67133 const wrapper = createWrapper ( ) ;
68- wrapper . vm . dataForm . name = 'valid_model' ; // Invalid length
69- wrapper . vm . dataForm . license = 'apache-2.0' ; // Invalid length
134+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
135+ wrapper . vm . dataForm . owner = 'testuser' ;
136+ wrapper . vm . dataForm . name = 'valid_model' ;
137+ wrapper . vm . dataForm . license = 'MIT' ;
70138 await triggerFormButton ( wrapper ) ;
71- expect ( wrapper . find ( '.el-form-item__error' ) . exists ( ) ) . toBe ( false ) ;
139+
140+ await waitForExpect ( ( ) => {
141+ expect ( wrapper . find ( '.el-form-item__error' ) . exists ( ) ) . toBe ( false ) ;
142+ } ) ;
72143 } ) ;
73144
74145 it ( "validates owner selection" , async ( ) => {
75146 const wrapper = createWrapper ( ) ;
76- wrapper . vm . dataForm . owner = '' ; // Invalid owner
147+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
148+ wrapper . vm . dataForm . owner = '' ; // Set invalid owner
77149 await triggerFormButton ( wrapper ) ;
78- expect ( wrapper . find ( '.el-form-item__error' ) . exists ( ) ) . toBe ( true ) ;
150+
151+ await waitForExpect ( ( ) => {
152+ const formErrors = wrapper . findAll ( '.el-form-item__error' ) ;
153+ const ownerError = formErrors . filter ( e => e . text ( ) . includes ( 'all.pleaseSelect' ) ) ;
154+ expect ( ownerError . length ) . toBeGreaterThan ( 0 ) ;
155+ } ) ;
79156 } ) ;
80157 } ) ;
81158
82159 describe ( "form submission" , ( ) => {
83- it ( "shows success message on successful submission" , async ( ) => {
160+ it ( "redirects on successful submission" , async ( ) => {
84161 const wrapper = createWrapper ( ) ;
162+ await new Promise ( resolve => setTimeout ( resolve , 0 ) ) ;
85163
164+ // Fill the form with valid data
86165 wrapper . vm . dataForm = {
87166 owner : 'testuser' ,
88167 name : 'valid-model' ,
@@ -92,23 +171,11 @@ describe("NewModel", () => {
92171 visibility : 'public'
93172 } ;
94173
95- // Mock the API response
96- vi . mock ( '../../../packs/useFetchApi' , ( ) => ( {
97- default : ( ) => ( {
98- post : ( ) => ( {
99- json : ( ) => Promise . resolve ( {
100- data : { value : { data : { path : 'testuser/testmodel' } } } ,
101- error : { value : null }
102- } )
103- } )
104- } )
105- } ) ) ;
106-
107174 await triggerFormButton ( wrapper ) ;
108-
109- // validate href is correct
110- expect ( window . location . href ) . toBe ( '/models/testuser/testmodel' ) ;
175+
176+ await waitForExpect ( ( ) => {
177+ expect ( window . location . href ) . toBe ( '/models/testuser/testmodel' ) ;
178+ } ) ;
111179 } ) ;
112-
113180 } ) ;
114181} ) ;
0 commit comments