11import Feature from './Feature'
22import MemoryAdapter from './MemoryAdapter'
3+ import MemoryInstrumenter from './instrumenters/MemoryInstrumenter'
34import { makeActor } from './testHelpers'
45
56let adapter : MemoryAdapter
@@ -11,6 +12,49 @@ describe('Feature', () => {
1112 feature = new Feature ( 'feature-1' , adapter , { } )
1213 } )
1314
15+ describe ( 'gate precedence (Ruby parity)' , ( ) => {
16+ test ( 'percentage of time wins over group when both are enabled' , async ( ) => {
17+ const { default : Dsl } = await import ( './Dsl.js' )
18+ const dsl = new Dsl ( adapter )
19+ dsl . register ( 'admins' , ( _actor : unknown ) => true )
20+
21+ const instrumenter = new MemoryInstrumenter ( )
22+ const featureWithGroups = new Feature ( 'precedence-feature' , adapter , dsl . groups , {
23+ instrumenter,
24+ } )
25+
26+ const actor = makeActor ( 1 )
27+ await featureWithGroups . enableGroup ( 'admins' )
28+ await featureWithGroups . enablePercentageOfTime ( 100 )
29+
30+ instrumenter . reset ( )
31+ expect ( await featureWithGroups . isEnabled ( actor ) ) . toEqual ( true )
32+ const event = instrumenter . eventByName ( 'feature_operation.flipper' )
33+ expect ( event ?. payload . gate_name ) . toEqual ( 'percentageOfTime' )
34+ } )
35+
36+ test ( 'expression evaluates using actor properties' , async ( ) => {
37+ const { default : Flipper } = await import ( './Flipper.js' )
38+
39+ const instrumenter = new MemoryInstrumenter ( )
40+ const featureWithInstrumenter = new Feature ( 'expression-actor-props' , adapter , { } , {
41+ instrumenter,
42+ } )
43+
44+ await featureWithInstrumenter . enableExpression ( Flipper . property ( 'admin' ) )
45+
46+ const actorWithProps = {
47+ flipperId : 'actor:1' ,
48+ flipperProperties : { admin : true } ,
49+ }
50+
51+ instrumenter . reset ( )
52+ expect ( await featureWithInstrumenter . isEnabled ( actorWithProps ) ) . toEqual ( true )
53+ const event = instrumenter . eventByName ( 'feature_operation.flipper' )
54+ expect ( event ?. payload . gate_name ) . toEqual ( 'expression' )
55+ } )
56+ } )
57+
1458 test ( 'has name' , ( ) => {
1559 expect ( feature . name ) . toEqual ( 'feature-1' )
1660 } )
@@ -32,6 +76,33 @@ describe('Feature', () => {
3276 expect ( await feature . isEnabled ( actor ) ) . toEqual ( false )
3377 } )
3478
79+ describe ( 'disableExpression' , ( ) => {
80+ test ( 'only clears the expression gate (does not clear other gate values)' , async ( ) => {
81+ const { default : Flipper } = await import ( './Flipper.js' )
82+
83+ const enabledActor = makeActor ( 1 )
84+ const expressionActor = {
85+ flipperId : 'actor:2' ,
86+ flipperProperties : { admin : true } ,
87+ }
88+
89+ await feature . enableActor ( enabledActor )
90+ await feature . enableExpression ( Flipper . property ( 'admin' ) )
91+
92+ // Expression is active pre-disable.
93+ expect ( await feature . isEnabled ( expressionActor ) ) . toEqual ( true )
94+
95+ await feature . disableExpression ( )
96+
97+ // Actor enablement should remain.
98+ expect ( await feature . isEnabled ( enabledActor ) ) . toEqual ( true )
99+
100+ // Expression should be removed.
101+ expect ( await feature . isEnabled ( expressionActor ) ) . toEqual ( false )
102+ expect ( await feature . enabledGateNames ( ) ) . toEqual ( [ 'actor' ] )
103+ } )
104+ } )
105+
35106 describe ( 'disablePercentageOfActors' , ( ) => {
36107 test ( 'sets percentage to 0' , async ( ) => {
37108 await feature . enablePercentageOfActors ( 25 )
@@ -53,6 +124,18 @@ describe('Feature', () => {
53124 await feature . disablePercentageOfActors ( )
54125 expect ( await feature . percentageOfActorsValue ( ) ) . toEqual ( 0 )
55126 } )
127+
128+ test ( 'does not clear other gate values' , async ( ) => {
129+ const actor = makeActor ( 5 )
130+
131+ await feature . enableActor ( actor )
132+ await feature . enablePercentageOfActors ( 50 )
133+
134+ await feature . disablePercentageOfActors ( )
135+
136+ // Actor enablement should remain after disabling the percentage gate.
137+ expect ( await feature . isEnabled ( actor ) ) . toEqual ( true )
138+ } )
56139 } )
57140
58141 describe ( 'disablePercentageOfTime' , ( ) => {
@@ -75,6 +158,18 @@ describe('Feature', () => {
75158 await feature . disablePercentageOfTime ( )
76159 expect ( await feature . percentageOfTimeValue ( ) ) . toEqual ( 0 )
77160 } )
161+
162+ test ( 'does not clear other gate values' , async ( ) => {
163+ const actor = makeActor ( 5 )
164+
165+ await feature . enableActor ( actor )
166+ await feature . enablePercentageOfTime ( 50 )
167+
168+ await feature . disablePercentageOfTime ( )
169+
170+ // Actor enablement should remain after disabling the percentage gate.
171+ expect ( await feature . isEnabled ( actor ) ) . toEqual ( true )
172+ } )
78173 } )
79174
80175 describe ( 'state' , ( ) => {
0 commit comments