Skip to content

Commit

Permalink
Gavsum/ch37466/add invert flag and redirect suffix (#38)
Browse files Browse the repository at this point in the history
* suffix working, first component of flag invert

* fix keep alive redirects, more tests

* update readme

* naming and comments
  • Loading branch information
Gavsum authored Feb 23, 2021
1 parent da78447 commit ed52579
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 32 deletions.
21 changes: 12 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,12 @@ export default {

#### Arguments

| key | description | type |
| :------------- | ----------------------------------------------------------------------------------- | -------------------- |
| `requiredFlag` | If the given feature flag is false, the user will be redirected to the given route. | `string` |
| `to` | The path or object which vue router will push. | `string` or `object` |
| key | description | type |
| :------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------- |
| `requiredFlag` | If the given feature flag is false, the user will be redirected to the given route. | `string` |
| `to` | The path which vue router will push. Functions passed are expected to resolve to a valid path. | `string`, `object`, or `function` |
| `invertFlag` | If set to true the the inverse of the requiredFlag's value will be used. | `boolean` |


### LDRouteGuard Component

Expand All @@ -120,11 +122,12 @@ const route = {

#### Props

| key | description | type |
| :------------- | ----------------------------------------------------------------------------------- | -------------------- |
| `component` | The component to be rendered given the required feature flag is true. | `vue component` |
| `requiredFlag` | If the given feature flag is false, the user will be redirected to the given route. | `string` |
| `to` | The path or object which vue router will push. | `string` or `object` |
| key | description | type |
| :------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------- |
| `component` | The component to be rendered given the required feature flag is true. | `vue component` |
| `requiredFlag` | If the given feature flag is false, the user will be redirected to the given route. | `string` |
| `to` | The path which vue router will push. Functions passed are expected to resolve to a valid path. | `string`, `object`, or `function` |
| `invertFlag` | If set to true the the inverse of the requiredFlag's value will be used. | `boolean` |

## Development

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "vue-ld",
"version": "0.1.7",
"version": "0.1.8",
"description": "A Vue.js wrapper for the LaunchDarkly SDK for Browser JavaScript",
"main": "dist/index.cjs.js",
"module": "dist/index.es.js",
Expand Down
5 changes: 3 additions & 2 deletions src/components/LDRouteGuard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ export default {
props: {
component: { type: [Function, Object, Promise], required: true },
requiredFeatureFlag: { type: String, required: true },
to: { type: [String, Object], required: true },
to: { type: [String, Object, Function], required: true },
invertFlag: { type: Boolean, required: false, default: false },
},
computed: {
show() {
return this.$ld.ready && this.$ld.flags[this.requiredFeatureFlag];
return this.$ld.ready && this.ldRedirectFlagValue;
},
importedComponent() {
// Handle dynamically imported components
Expand Down
51 changes: 39 additions & 12 deletions src/mixins/ldRedirect.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
export default (requiredFeatureFlag, to) => {
export default (requiredFeatureFlag, to, invertFlag) => {
return {
data() {
return {
ldRedirectReadyWatcher: null,
ldRedirectFlagWatcher: null,
ldRedirectHasBeenDeactivated: false,
};
},
computed: {
ldRedirectFlagValue() {
return this.invertFlag || invertFlag
? !this.$ld.flags[requiredFeatureFlag || this.requiredFeatureFlag]
: this.$ld.flags[requiredFeatureFlag || this.requiredFeatureFlag];
},
ldRedirectShouldRedirect() {
return this.$ld.ready && !this.$ld.flags[requiredFeatureFlag || this.requiredFeatureFlag];
return this.$ld.ready && !this.ldRedirectFlagValue;
},
ldRedirectShouldDestroy() {
return this.$ld.ready && this.$ld.flags[requiredFeatureFlag || this.requiredFeatureFlag];
return this.$ld.ready && this.ldRedirectFlagValue;
},
ldRedirectResolveTo() {
// handles 'to' redirect values passed as functions
const redirectVal = to == null ? this.ldRedirectTo : to;
if (typeof redirectVal === 'function') {
const boundRedirectTo = redirectVal.bind(this);
return boundRedirectTo();
}
return redirectVal;
},
},
methods: {
Expand All @@ -22,7 +37,7 @@ export default (requiredFeatureFlag, to) => {
},
() => {
if (this.ldRedirectShouldRedirect) {
this.$router.push(to == null ? this.ldRedirectTo : to);
this.$router.push(this.ldRedirectResolveTo);
} else if (this.ldRedirectShouldDestroy) {
this.ldRedirectDestroyWatchers();
}
Expand All @@ -32,11 +47,11 @@ export default (requiredFeatureFlag, to) => {
setLdRedirectFlagWatcher() {
this.ldRedirectFlagWatcher = this.$watch(
() => {
return this.$ld.flags[requiredFeatureFlag || this.requiredFeatureFlag];
return this.ldRedirectFlagValue;
},
() => {
if (this.ldRedirectShouldRedirect) {
this.$router.push(to == null ? this.ldRedirectTo : to);
this.$router.push(this.ldRedirectResolveTo);
} else if (this.ldRedirectShouldDestroy) {
this.ldRedirectDestroyWatchers();
}
Expand All @@ -53,14 +68,26 @@ export default (requiredFeatureFlag, to) => {
this.ldRedirectFlagWatcher = null;
}
},
ldRedirectHandler() {
if (this.$ld.ready && !this.ldRedirectFlagValue) {
this.$router.push(this.ldRedirectResolveTo);
} else if (!this.ldReady) {
this.setLdRedirectReadyWatcher();
this.setLdRedirectFlagWatcher();
}
},
},
mounted() {
if (this.$ld.ready && !this.$ld.flags[requiredFeatureFlag || this.requiredFeatureFlag]) {
this.$router.push(to == null ? this.ldRedirectTo : to);
} else if (!this.ldReady) {
this.setLdRedirectReadyWatcher();
this.setLdRedirectFlagWatcher();
activated() {
// activated lifecycle trigger used for keep-alive components
if (this.ldRedirectHasBeenDeactivated) {
this.ldRedirectHandler();
}
},
mounted() {
this.ldRedirectHandler();
},
deactivated() {
this.ldRedirectHasBeenDeactivated = true;
},
};
};
46 changes: 42 additions & 4 deletions tests/unit/ld-redirect.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ const Component = {
template: '<div></div>',
};

const mixins = [ldRedirect('myFlag', '/')];
const createMixin = (props) => {
return props
? [ldRedirect(props.flag, props.redirect, props.invertFlag)]
: [ldRedirect('myFlag', '/', false)];
};

describe('ldRedirectMixin', () => {
let server;
Expand All @@ -23,7 +27,7 @@ describe('ldRedirectMixin', () => {
let localVue;
let mocks;
let wrapper;
const finishSetup = async () => {
const finishSetup = async (mixins) => {
localVue = createLocalVue();
localVue.use(VueLd, vueLdOptions);
mocks = {
Expand All @@ -45,7 +49,7 @@ describe('ldRedirectMixin', () => {
const flags = cloneDeep(flagsResponse);
flags.myFlag.value = false;
server.respondWith([200, { 'Content-Type': 'application/json' }, JSON.stringify(flags)]);
await finishSetup();
await finishSetup(createMixin());
expect(wrapper.vm.$ld.flags.myFlag).toBe(false);
expect(wrapper.vm.$router.push).toHaveBeenCalled();
});
Expand All @@ -56,10 +60,44 @@ describe('ldRedirectMixin', () => {
{ 'Content-Type': 'application/json' },
JSON.stringify(flagsResponse),
]);
await finishSetup();
await finishSetup(createMixin());
expect(wrapper.vm.$ld.flags.myFlag).toBe(true);
expect(wrapper.vm.$router.push).not.toHaveBeenCalled();
expect(wrapper.vm.ldRedirectReadyWatcher).toBe(null);
expect(wrapper.vm.ldRedirectFlagWatcher).toBe(null);
});

it('correctly handles redirect as object', async () => {
const flags = cloneDeep(flagsResponse);
flags.myFlag.value = false;
server.respondWith([200, { 'Content-Type': 'application/json' }, JSON.stringify(flags)]);
const redirectObj = { to: 'some.route' };
await finishSetup(createMixin({ flag: 'myFlag', redirect: redirectObj, invertFlag: false }));
expect(wrapper.vm.$ld.flags.myFlag).toBe(false);
expect(wrapper.vm.$router.push).toBeCalledWith(redirectObj);
});

it('correctly handles redirect as function', async () => {
const flags = cloneDeep(flagsResponse);
flags.myFlag.value = false;
server.respondWith([200, { 'Content-Type': 'application/json' }, JSON.stringify(flags)]);
const redirectObj = { to: 'some.route' };
const redirectFunc = () => {
return redirectObj;
};
await finishSetup(createMixin({ flag: 'myFlag', redirect: redirectFunc, invertFlag: false }));
expect(wrapper.vm.$ld.flags.myFlag).toBe(false);
expect(wrapper.vm.$router.push).toBeCalledWith(redirectObj);
});

it('redirects on true featureflag if invertFlag is set', async () => {
const flags = cloneDeep(flagsResponse);
flags.myFlag.value = true;
server.respondWith([200, { 'Content-Type': 'application/json' }, JSON.stringify(flags)]);
await finishSetup(
createMixin({ flag: 'myFlag', redirect: { to: 'some.route' }, invertFlag: true })
);
expect(wrapper.vm.$ld.flags.myFlag).toBe(true);
expect(wrapper.vm.$router.push).toHaveBeenCalled();
});
});
20 changes: 16 additions & 4 deletions tests/unit/ld-route-guard.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('ldRedirectMixin', () => {
let localVue;
let mocks;
let wrapper;
const finishSetup = async (component = EmptyComponent) => {
const finishSetup = async (component, invertFlag) => {
localVue = createLocalVue();
localVue.use(VueLd, vueLdOptions);
mocks = {
Expand All @@ -41,6 +41,7 @@ describe('ldRedirectMixin', () => {
component,
requiredFeatureFlag: 'myFlag',
to: '/',
invertFlag,
},
});
await ldClientReady(wrapper);
Expand All @@ -54,7 +55,7 @@ describe('ldRedirectMixin', () => {
const flags = cloneDeep(flagsResponse);
flags.myFlag.value = false;
server.respondWith([200, { 'Content-Type': 'application/json' }, JSON.stringify(flags)]);
await finishSetup();
await finishSetup(EmptyComponent, false);
expect(wrapper.vm.$router.push).toHaveBeenCalled();
expect(wrapper.findComponent(EmptyComponent).exists()).toBe(false);
});
Expand All @@ -65,7 +66,7 @@ describe('ldRedirectMixin', () => {
server.respondWith([200, { 'Content-Type': 'application/json' }, JSON.stringify(flags)]);

const dynamicEmptyComponent = new Promise((resolve) => resolve(EmptyComponent));
await finishSetup(dynamicEmptyComponent);
await finishSetup(dynamicEmptyComponent, false);
expect(wrapper.vm.$router.push).toHaveBeenCalled();
});

Expand All @@ -75,8 +76,19 @@ describe('ldRedirectMixin', () => {
{ 'Content-Type': 'application/json' },
JSON.stringify(flagsResponse),
]);
await finishSetup();
await finishSetup(EmptyComponent, false);
expect(wrapper.vm.$router.push).not.toHaveBeenCalled();
expect(wrapper.findComponent(EmptyComponent).exists()).toBe(true);
});

it('redirects with featureflag if invertFlag is set', async () => {
server.respondWith([
200,
{ 'Content-Type': 'application/json' },
JSON.stringify(flagsResponse),
]);
await finishSetup(EmptyComponent, true);
expect(wrapper.vm.$router.push).toHaveBeenCalled();
expect(wrapper.findComponent(EmptyComponent).exists()).toBe(false);
});
});

0 comments on commit ed52579

Please sign in to comment.