Skip to content

Commit

Permalink
feat(feature-flags): rename exports, add merge, and update tests (#7802)
Browse files Browse the repository at this point in the history
* feat(feature-flags): rename exports, add merge, and update tests

* feat(feature-flags): add support for process.env checking for flags

* feat(feature-flags): add sass module variant with behavior that matches JS

* feat(project): integrate feature-flags with react package

* docs(feature-flags): update README

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
joshblack and kodiakhq[bot] authored Mar 1, 2021
1 parent afd30d7 commit 3861277
Show file tree
Hide file tree
Showing 13 changed files with 389 additions and 167 deletions.
103 changes: 48 additions & 55 deletions packages/feature-flags/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,83 +24,76 @@ The `@carbon/feature-flags` provides a runtime-based feature flag system that
you can use to enable or disable experimental features from Carbon or in your
own code.

To enable a feature flag, you will need to enable it in each of the environments
that you're using it in. Often times, this will mean JavaScript, Sass, or both.

To enable a flag in JavaScript, you would use `enableFeatureFlag`:
To check if a feature flag is enabled, you can use the `enabled` function in
JavaScript:

```js
import { enableFeatureFlag } from '@carbon/feature-flags';
import { enabled } from '@carbon/feature-flags';

enableFeatureFlag('feature-flag-name');
enabled('feature-flag-name');
```

To enable a flag in Sass, you would set the `$feature-flags` variable:
In Sass, you would use the `enabled` function or mixin:

```scss
@import '@carbon/feature-flags/scss/feature-flags';
@use '@carbon/feature-flags';

$feature-flags: map-merge(
$feature-flags,
(
'feature-flag-name': true,
)
);
```
// Return true if the flag is enabled
@if feature-flags.enabled('feature-flag-name') {
//
}

### Managing and using feature flags
@include enabled('feature-flag-name') {
// only include contents if the flag is enabled
}
```

You can use the `@carbon/feature-flags` package to build on top of existing
feature flags, or to add your own.
### Managing feature flags

You can add and toggle flags in JavaScript and Sass. In JavaScript, this would
look like:
You can change whether a feature flag is enabled. In JavaScript, you can use the
`enable`, `disable`, and `merge` functions to accomplish this.

```js
import {
addFeatureFlag,
enableFeatureFlag,
disableFeatureFlag,
featureFlagEnabled,
} from '@carbon/feature-flags';

// Specify a default value for the flag
addFeatureFlag('feature-flag-name', false);

// You can use `featureFlagEnabled` to conditionally run
// branches of your code
if (featureFlagEnabled('feature-flag-name')) {
// Run code if the flag is enabled
}
import { enable, disable, merge } from '@carbon/feature-flags';

// You can also modify the value of the flag
disableFeatureFlag('feature-flag-name');
enableFeatureFlag('feature-flag-name');
// Enable `feature-flag-a`
enable('feature-flag-a');

// Disable `feature-flag-a`
disable('feature-flag-a);
// Set a variety of feature flags to a specific value
merge({
'feature-flag-a': true,
'feature-flag-b': false,
'feature-flag-c': true,
});
```
In Sass, you would write the following:
In Sass, you can configure whether a feature flag is enabled when you include
the module or by using `enable`, `disable`, and `merge`.
```scss
@import '@carbon/feature-flags/scss/feature-flags';
@use '@carbon/feature-flags' with (
$feature-flags: (
'feature-flag-a': false,
'feature-flag-b': true,
),
);
// Enable `feature-flag-a`
@include feature-flags.enable('feature-flag-a');
// Disable `feature-flag-b`
@include feature-flags.disable('feature-flag-b');
$feature-flags: map-merge(
$feature-flags,
// Set a variety of feature flags to a specific value
@include feature-flags.merge(
(
'feature-flag-name': true,
'feature-flag-a': true,
'feature-flag-b': true,
)
);

@if feature-flag-enabled('feature-flag-name') {
// ...
}

// You can also run this as a mixin to conditionally include
// code
.my-selector {
@include feature-flag-enabled('feature-flag-name') {
// ...
}
}
```
## 🙌 Contributing
Expand Down
40 changes: 0 additions & 40 deletions packages/feature-flags/__tests__/feature-flags-test.js

This file was deleted.

157 changes: 125 additions & 32 deletions packages/feature-flags/__tests__/scss-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,49 +9,142 @@

'use strict';

const { convert, createSassRenderer } = require('@carbon/test-utils/scss');
const { SassRenderer } = require('@carbon/test-utils/scss');

const render = createSassRenderer(__dirname);
const { render } = SassRenderer.create(__dirname);

describe('feature-flags.scss', () => {
it('should support default feature flags before the import', async () => {
const { calls } = await render(`
$feature-flags: ('test': true);
describe('@carbon/feature-flags', () => {
describe('add', () => {
it('should add the given flag and set whether its enabled', async () => {
const { getValue } = await render(`
@use '../index.scss' as feature-flags;
@import '../scss/feature-flags';
@include feature-flags.add(flag-a, true);
@include feature-flags.add(flag-b, false);
@if feature-flag-enabled('test') {
$t: test(true);
}
`);
$_: get-value(feature-flags.enabled(flag-a));
$_: get-value(feature-flags.enabled(flag-b));
`);

expect(calls.length).toBe(1);
expect(convert(calls[0][0])).toBe(true);
// flag-a
expect(getValue(0)).toBe(true);
// flag-b
expect(getValue(1)).toBe(false);
});
});

it('should support modifying flags', async () => {
const { calls } = await render(`
@import '../scss/feature-flags';
describe('enable', () => {
it('should enable the given feature flag', async () => {
const { getValue } = await render(`
@use '../index.scss' as feature-flags;
$feature-flags: map-merge($feature-flags, (
'test': true,
));
@include feature-flags.add(flag-a, false);
@if feature-flag-enabled('test') {
$t: test('feature-flag-enabled');
}
$_: get-value(feature-flags.enabled(flag-a));
$feature-flags: map-merge($feature-flags, (
'test': false,
));
@include feature-flags.enable(flag-a);
@if not feature-flag-enabled('test') {
$t: test('feature-flag-disabled');
}
`);
$_: get-value(feature-flags.enabled(flag-a));
`);

expect(calls.length).toBe(2);
expect(convert(calls[0][0])).toBe('feature-flag-enabled');
expect(convert(calls[1][0])).toBe('feature-flag-disabled');
expect(getValue(0)).toBe(false);
expect(getValue(1)).toBe(true);
});
});

describe('disable', () => {
it('should disable the given feature flag', async () => {
const { getValue } = await render(`
@use '../index.scss' as feature-flags;
@include feature-flags.add(flag-a, true);
$_: get-value(feature-flags.enabled(flag-a));
@include feature-flags.disable(flag-a);
$_: get-value(feature-flags.enabled(flag-a));
`);

expect(getValue(0)).toBe(true);
expect(getValue(1)).toBe(false);
});
});

describe('enabled', () => {
it('should return whether a flag is enabled or disabled', async () => {
const { getValue } = await render(`
@use '../index.scss' as feature-flags;
@include feature-flags.add(flag-a, true);
@include feature-flags.add(flag-b, false);
$_: get-value(feature-flags.enabled(flag-a));
$_: get-value(feature-flags.enabled(flag-b));
`);

// flag-a
expect(getValue(0)).toBe(true);
// flag-b
expect(getValue(1)).toBe(false);
});
});

describe('merge', () => {
it('should set each feature flag given', async () => {
const { getValue } = await render(`
@use '../index.scss' as feature-flags;
@include feature-flags.add(flag-c, false);
@include feature-flags.merge((
flag-a: true,
flag-b: false,
flag-c: true,
));
$_: get-value(feature-flags.enabled(flag-a));
$_: get-value(feature-flags.enabled(flag-b));
$_: get-value(feature-flags.enabled(flag-c));
`);

// flag-a
expect(getValue(0)).toBe(true);
// flag-b
expect(getValue(1)).toBe(false);
// flag-c
expect(getValue(2)).toBe(true);
});
});

// it('should support default feature flags before the import', async () => {
// const { calls } = await render(`
// $feature-flags: ('test': true);
// @import '../scss/feature-flags';
// @if feature-flag-enabled('test') {
// $t: test(true);
// }
// `);
// expect(calls.length).toBe(1);
// expect(convert(calls[0][0])).toBe(true);
// });
// it('should support modifying flags', async () => {
// const { calls } = await render(`
// @import '../scss/feature-flags';
// $feature-flags: map-merge($feature-flags, (
// 'test': true,
// ));
// @if feature-flag-enabled('test') {
// $t: test('feature-flag-enabled');
// }
// $feature-flags: map-merge($feature-flags, (
// 'test': false,
// ));
// @if not feature-flag-enabled('test') {
// $t: test('feature-flag-disabled');
// }
// `);
// expect(calls.length).toBe(2);
// expect(convert(calls[0][0])).toBe('feature-flag-enabled');
// expect(convert(calls[1][0])).toBe('feature-flag-disabled');
// });
});
5 changes: 5 additions & 0 deletions packages/feature-flags/feature-flags.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ feature-flags:
- name: enable-css-custom-properties
description: Describe what the flag does
enabled: false
- name: enable-use-controlled-state-with-value
description: >
Enable components to be created in either a controlled or uncontrolled
mode
enabled: false
Loading

0 comments on commit 3861277

Please sign in to comment.