@@ -49,15 +49,29 @@ describe("~/pages/admin/System.tsx", () => {
4949 . reply ( 200 , { version : "1.0.0" } ) ;
5050 } ;
5151
52+ const fetchDomainsScope = ( ) => {
53+ return nock ( process . env . API_DOMAIN || "" )
54+ . get ( "/admin/domains" )
55+ . reply ( 200 , {
56+ domains : {
57+ dev : "https://dev.example.org" ,
58+ api : "https://api.example.org" ,
59+ app : "https://app.example.org" ,
60+ } ,
61+ } ) ;
62+ } ;
63+
5264 beforeEach ( async ( ) => {
5365 const scope = fetchRuntimesScope ( [ "node@24" , "python@3" ] ) ;
5466 const scopeVersion = fetchMiseVersionScope ( ) ;
67+ const scopeDomains = fetchDomainsScope ( ) ;
5568
5669 createWrapper ( ) ;
5770
5871 await waitFor ( ( ) => {
5972 expect ( scopeVersion . isDone ( ) ) . toBe ( true ) ;
6073 expect ( scope . isDone ( ) ) . toBe ( true ) ;
74+ expect ( scopeDomains . isDone ( ) ) . toBe ( true ) ;
6175 } ) ;
6276 } ) ;
6377
@@ -69,9 +83,13 @@ describe("~/pages/admin/System.tsx", () => {
6983 expect ( wrapper . getByText ( "Installed runtimes" ) ) . toBeTruthy ( ) ;
7084 expect (
7185 wrapper . getByText (
72- "Manage runtimes that are installed on your Stormkit instance. "
86+ "Manage runtimes that are installed on your Stormkit instance"
7387 )
7488 ) . toBeTruthy ( ) ;
89+ expect ( wrapper . getByText ( "Domains" ) ) . toBeTruthy ( ) ;
90+ expect (
91+ wrapper . getByText ( "Configure custom domains for your Stormkit instance" )
92+ ) . toBeTruthy ( ) ;
7593 } ) ;
7694
7795 it ( "should submit the form" , async ( ) => {
@@ -89,7 +107,9 @@ describe("~/pages/admin/System.tsx", () => {
89107 // Turn off auto-install
90108 await fireEvent . click ( wrapper . getByLabelText ( "Auto install" ) ) ;
91109
92- await fireEvent . click ( wrapper . getByText ( "Save" ) ) ;
110+ // Find the save button in the runtimes section specifically
111+ const saveButtons = wrapper . getAllByText ( "Save" ) ;
112+ await fireEvent . click ( saveButtons [ 0 ] ) ; // First save button is for runtimes
93113
94114 await waitFor ( ( ) => {
95115 expect ( scope . isDone ( ) ) . toBe ( true ) ;
@@ -101,7 +121,7 @@ describe("~/pages/admin/System.tsx", () => {
101121 expect ( wrapper . getByText ( "Mise" ) ) ;
102122 expect (
103123 wrapper . getByText (
104- "Stormkit relies on open-source mise for runtime management. "
124+ "Stormkit relies on open-source mise for runtime management"
105125 )
106126 ) ;
107127 expect ( wrapper . getByText ( "Upgrade to latest" ) ) ;
@@ -329,4 +349,142 @@ describe("~/pages/admin/System.tsx", () => {
329349 } ) ;
330350 } ) ;
331351 } ) ;
352+
353+ describe ( "Domains" , ( ) => {
354+ it ( "should render domain configuration form" , async ( ) => {
355+ await waitFor ( ( ) => {
356+ expect ( wrapper . getByText ( "Domains" ) ) . toBeTruthy ( ) ;
357+ expect (
358+ wrapper . getByText (
359+ "Configure custom domains for your Stormkit instance"
360+ )
361+ ) . toBeTruthy ( ) ;
362+ expect ( wrapper . getByLabelText ( "API Domain" ) ) . toBeTruthy ( ) ;
363+ expect ( wrapper . getByLabelText ( "App Domain" ) ) . toBeTruthy ( ) ;
364+ expect ( wrapper . getByLabelText ( "Dev Domain" ) ) . toBeTruthy ( ) ;
365+ } ) ;
366+ } ) ;
367+
368+ it ( "should display fetched domain values" , async ( ) => {
369+ await waitFor ( ( ) => {
370+ const apiField = wrapper . getByDisplayValue ( "https://api.example.org" ) ;
371+ const appField = wrapper . getByDisplayValue ( "https://app.example.org" ) ;
372+ const devField = wrapper . getByDisplayValue ( "https://dev.example.org" ) ;
373+
374+ expect ( apiField ) . toBeTruthy ( ) ;
375+ expect ( appField ) . toBeTruthy ( ) ;
376+ expect ( devField ) . toBeTruthy ( ) ;
377+ } ) ;
378+ } ) ;
379+
380+ it ( "should display helper texts for domain fields" , async ( ) => {
381+ await waitFor ( ( ) => {
382+ expect (
383+ wrapper . getByText ( "API requests will be served from this domain" )
384+ ) . toBeTruthy ( ) ;
385+ expect (
386+ wrapper . getByText (
387+ "This domain will be used to access your Stormkit dashboard"
388+ )
389+ ) . toBeTruthy ( ) ;
390+ expect (
391+ wrapper . getByText (
392+ "Deployment previews will be displayed using subdomains of this domain"
393+ )
394+ ) . toBeTruthy ( ) ;
395+ } ) ;
396+ } ) ;
397+
398+ it ( "should handle domain fetch error" , async ( ) => {
399+ // Create a new wrapper with error response
400+ const errorScope = nock ( process . env . API_DOMAIN || "" )
401+ . get ( "/admin/system/runtimes" )
402+ . reply ( 200 , {
403+ runtimes : [ ] ,
404+ autoInstall : true ,
405+ installed : { } ,
406+ status : "ok" ,
407+ } ) ;
408+
409+ const errorMiseScope = nock ( process . env . API_DOMAIN || "" )
410+ . get ( "/admin/system/mise" )
411+ . reply ( 200 , { version : "1.0.0" } ) ;
412+
413+ const errorDomainsScope = nock ( process . env . API_DOMAIN || "" )
414+ . get ( "/admin/domains" )
415+ . reply ( 500 , { error : "Internal server error" } ) ;
416+
417+ const errorWrapper = render ( < AdminSystem /> ) ;
418+
419+ await waitFor ( ( ) => {
420+ expect ( errorScope . isDone ( ) ) . toBe ( true ) ;
421+ expect ( errorMiseScope . isDone ( ) ) . toBe ( true ) ;
422+ expect ( errorDomainsScope . isDone ( ) ) . toBe ( true ) ;
423+ } ) ;
424+
425+ await waitFor ( ( ) => {
426+ expect (
427+ errorWrapper . getByText ( "Something went wrong while fetching domains" )
428+ ) . toBeTruthy ( ) ;
429+ } ) ;
430+ } ) ;
431+
432+ it ( "should submit domain configuration successfully" , async ( ) => {
433+ const postScope = nock ( process . env . API_DOMAIN || "" )
434+ . post ( "/admin/domains" , {
435+ api : "https://new-api.example.org" ,
436+ app : "https://new-app.example.org" ,
437+ dev : "https://new-dev.example.org" ,
438+ } )
439+ . reply ( 200 , { ok : true } ) ;
440+
441+ await waitFor ( ( ) => {
442+ const apiField = wrapper . getByDisplayValue ( "https://api.example.org" ) ;
443+ const appField = wrapper . getByDisplayValue ( "https://app.example.org" ) ;
444+ const devField = wrapper . getByDisplayValue ( "https://dev.example.org" ) ;
445+
446+ fireEvent . change ( apiField , {
447+ target : { value : "https://new-api.example.org" } ,
448+ } ) ;
449+ fireEvent . change ( appField , {
450+ target : { value : "https://new-app.example.org" } ,
451+ } ) ;
452+ fireEvent . change ( devField , {
453+ target : { value : "https://new-dev.example.org" } ,
454+ } ) ;
455+ } ) ;
456+
457+ // Find the save button in the domains section specifically (second save button)
458+ const saveButtons = wrapper . getAllByText ( "Save" ) ;
459+ fireEvent . click ( saveButtons [ 1 ] ) ; // Second save button is for domains
460+
461+ await waitFor ( ( ) => {
462+ expect ( postScope . isDone ( ) ) . toBe ( true ) ;
463+ } ) ;
464+ } ) ;
465+
466+ it ( "should handle domain update error" , async ( ) => {
467+ const postScope = nock ( process . env . API_DOMAIN || "" )
468+ . post ( "/admin/domains" )
469+ . reply ( 400 , { error : "Invalid domain" } ) ;
470+
471+ await waitFor ( ( ) => {
472+ const apiField = wrapper . getByDisplayValue ( "https://api.example.org" ) ;
473+ fireEvent . change ( apiField , { target : { value : "invalid-domain" } } ) ;
474+ } ) ;
475+
476+ // Find the save button in the domains section specifically (second save button)
477+ const saveButtons = wrapper . getAllByText ( "Save" ) ;
478+ fireEvent . click ( saveButtons [ 1 ] ) ; // Second save button is for domains
479+
480+ await waitFor ( ( ) => {
481+ expect ( postScope . isDone ( ) ) . toBe ( true ) ;
482+ expect (
483+ wrapper . getByText (
484+ "An error occurred while updating domains. Make sure specified domains are valid."
485+ )
486+ ) . toBeTruthy ( ) ;
487+ } ) ;
488+ } ) ;
489+ } ) ;
332490} ) ;
0 commit comments