1+ import { RegistryContext } from "@effect/atom-react" ;
2+ import { Atom , AtomRegistry } from "effect/reactivity" ;
13import {
24 DEFAULT_UNIFIED_SETTINGS ,
35 EnvironmentId ,
@@ -10,7 +12,7 @@ import { describe, expect, it, vi } from "vite-plus/test";
1012import { page , userEvent } from "vite-plus/test/browser" ;
1113
1214import "~/index.css" ;
13- import { render } from "../../../test/render" ;
15+ import { act , render } from "../../../test/render" ;
1416import { ScheduledTasksSettings } from "./ScheduledTasksSettings" ;
1517
1618const environmentId = EnvironmentId . make ( "laptop" ) ;
@@ -38,7 +40,23 @@ const task: ScheduledTask = {
3840 runCount : 0 ,
3941} ;
4042
41- const state = vi . hoisted ( ( ) => ( { connected : true , tasksLoaded : true } ) ) ;
43+ const state = vi . hoisted ( ( ) => ( { connected : true , tasksLoaded : true , secondary : false } ) ) ;
44+ const secondaryId = EnvironmentId . make ( "secondary" ) ;
45+ const permissions = new Map < string , Atom . Writable < boolean > > ( ) ;
46+
47+ function setup ( ) {
48+ state . connected = true ;
49+ state . tasksLoaded = true ;
50+ state . secondary = false ;
51+ permissions . clear ( ) ;
52+ permissions . set ( environmentId , Atom . make ( true ) ) ;
53+ permissions . set ( secondaryId , Atom . make ( true ) ) ;
54+ permissions . set ( "" , Atom . make ( false ) ) ;
55+ }
56+
57+ function secondaryEnvironment ( ) {
58+ return { ...environment ( ) , environmentId : secondaryId , label : "Secondary" } ;
59+ }
4260
4361function environment ( ) {
4462 return {
@@ -54,8 +72,12 @@ vi.mock(import("./SettingsScopeContext"), async (original) => {
5472 const entry = environment ( ) ;
5573 return {
5674 scope : { kind : "all" , label : "All" , members : [ ] , environmentIds : [ environmentId ] } ,
57- environments : [ entry ] ,
58- connectedEnvironments : state . connected ? [ entry ] : [ ] ,
75+ environments : state . secondary ? [ entry , secondaryEnvironment ( ) ] : [ entry ] ,
76+ connectedEnvironments : state . connected
77+ ? state . secondary
78+ ? [ entry , secondaryEnvironment ( ) ]
79+ : [ entry ]
80+ : [ ] ,
5981 environment : entry ,
6082 target : null ,
6183 targets : [ ] ,
@@ -68,15 +90,14 @@ vi.mock(import("./SettingsScopeContext"), async (original) => {
6890} ) ;
6991vi . mock ( import ( "../../state/server" ) , async ( original ) => {
7092 const actual = await original ( ) ;
71- const { Atom } = await import ( "effect/reactivity" ) ;
72- const allowed = Atom . make ( true ) ;
93+
7394 return {
7495 ...actual ,
7596 serverEnvironment : {
7697 ...actual . serverEnvironment ,
7798 upsertScheduledTask : {
7899 ...actual . serverEnvironment . upsertScheduledTask ,
79- permissionAtom : ( ) => allowed ,
100+ permissionAtom : ( id : string | null ) => permissions . get ( id ?? "" ) ! ,
80101 } ,
81102 } ,
82103 } ;
@@ -93,7 +114,8 @@ vi.mock(import("../../state/query"), async (original) => ({
93114} ) ) ;
94115vi . mock ( import ( "../../state/environments" ) , async ( original ) => ( {
95116 ...( await original ( ) ) ,
96- useEnvironment : ( ( ) => environment ( ) ) as never ,
117+ useEnvironment : ( ( id : EnvironmentId ) =>
118+ id === secondaryId ? secondaryEnvironment ( ) : environment ( ) ) as never ,
97119} ) ) ;
98120vi . mock ( import ( "../../state/entities" ) , async ( original ) => ( {
99121 ...( await original ( ) ) ,
@@ -105,9 +127,44 @@ vi.mock(import("../../hooks/useSettings"), async (original) => ({
105127} ) ) ;
106128
107129describe ( "ScheduledTasksSettings" , ( ) => {
130+ it ( "creates on a permitted secondary and reacts to permission changes" , async ( ) => {
131+ setup ( ) ;
132+ state . secondary = true ;
133+ const registry = AtomRegistry . make ( ) ;
134+ using _registry = { [ Symbol . dispose ] : ( ) => registry . dispose ( ) } ;
135+ registry . set ( permissions . get ( environmentId ) ! , false ) ;
136+ await using screen = await render (
137+ < RegistryContext . Provider value = { registry } >
138+ < ScheduledTasksSettings />
139+ </ RegistryContext . Provider > ,
140+ ) ;
141+ const create = screen . getByRole ( "button" , { name : "New task" } ) ;
142+ await expect . element ( create ) . toBeEnabled ( ) ;
143+ await act ( ( ) => registry . set ( permissions . get ( secondaryId ) ! , false ) ) ;
144+ await expect . element ( create ) . toBeDisabled ( ) ;
145+ await act ( ( ) => registry . set ( permissions . get ( secondaryId ) ! , true ) ) ;
146+ await expect . element ( create ) . toBeEnabled ( ) ;
147+ await create . click ( ) ;
148+ await expect . element ( page . getByLabelText ( "Runs on" ) ) . toHaveTextContent ( "Secondary" ) ;
149+ } ) ;
150+
151+ it ( "keeps the permitted representative as the creation target" , async ( ) => {
152+ setup ( ) ;
153+ state . secondary = true ;
154+ await using screen = await render ( < ScheduledTasksSettings /> ) ;
155+ await screen . getByRole ( "button" , { name : "New task" } ) . click ( ) ;
156+ await expect . element ( page . getByLabelText ( "Runs on" ) ) . toHaveTextContent ( "Laptop" ) ;
157+ } ) ;
158+
159+ it ( "disables creation without a connected target" , async ( ) => {
160+ setup ( ) ;
161+ state . connected = false ;
162+ await using screen = await render ( < ScheduledTasksSettings /> ) ;
163+ await expect . element ( screen . getByRole ( "button" , { name : "New task" } ) ) . toBeDisabled ( ) ;
164+ } ) ;
165+
108166 it ( "keeps a linked task's unsaved edits across a disconnect and reconnect" , async ( ) => {
109- state . connected = true ;
110- state . tasksLoaded = true ;
167+ setup ( ) ;
111168 await using screen = await render ( < ScheduledTasksSettings taskId = { task . id } /> ) ;
112169 const name = page . getByRole ( "dialog" ) . getByRole ( "textbox" , { name : "Name" } ) ;
113170 await expect . element ( name ) . toHaveValue ( "Review issues" ) ;
@@ -124,7 +181,7 @@ describe("ScheduledTasksSettings", () => {
124181 } ) ;
125182
126183 it ( "keeps an editor the user opened when the linked task loads later" , async ( ) => {
127- state . connected = true ;
184+ setup ( ) ;
128185 state . tasksLoaded = false ;
129186 await using screen = await render ( < ScheduledTasksSettings taskId = { task . id } /> ) ;
130187 await userEvent . click ( page . getByRole ( "button" , { name : "New task" } ) ) ;
0 commit comments