Skip to content
This repository has been archived by the owner on Feb 27, 2022. It is now read-only.

Commit

Permalink
GitHub config (#1)
Browse files Browse the repository at this point in the history
* Add tests for FormLeaveGuardMixin
* Add basic GitLab CI
* Remove GitLab CI (using GitHub...)
* Update CHANGELOG
* Update CHANGELOG
* 0.2.2
  • Loading branch information
kendallroth committed Jul 1, 2020
1 parent 639d40e commit 2f9607d
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 30 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Expand Up @@ -4,7 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
## [0.2.2] - 2020-07-01
### Added
- GitLab testing and release pipeline (#1)
- Test suite implementation and coverage tests (#1)

### Fixed
- Only allow setting form flags that have been defined (#1)

## [0.2.1] - 2020-06-30
### Fixed
Expand Down
11 changes: 10 additions & 1 deletion babel.config.js
Expand Up @@ -9,7 +9,16 @@ module.exports = {
plugins: ["add-module-exports"],
},
test: {
presets: ["@babel/preset-env"],
presets: [
[
"@babel/preset-env",
{
targets: {
node: "current",
},
},
],
],
plugins: ["add-module-exports"],
},
},
Expand Down
14 changes: 9 additions & 5 deletions package.json
@@ -1,15 +1,16 @@
{
"name": "@kendallroth/vue-simple-forms",
"version": "0.2.1",
"version": "0.2.2",
"description": "Simple Vue form state management library",
"main": "./lib/index.js",
"scripts": {
"clean": "rimraf lib",
"format": "prettier --write {src,test}/**/*.js",
"test": "npm run lint && jest --verbose",
"test:cover": "npm test -- --coverage",
"test": "npm run lint && npm run test:only",
"test:cover": "npm run test:only -- --coverage",
"test:prod": "cross-env BABEL_ENV=production npm run test",
"test:watch": "npm test -- --watch",
"test:only": "jest --verbose",
"test:watch": "npm run test:only -- --watch",
"lint": "eslint src test",
"build": "cross-env BABEL_ENV=production babel src --out-dir lib",
"prepublish": "npm run clean && npm run test && npm run build"
Expand Down Expand Up @@ -64,6 +65,9 @@
}
},
"lint-staged": {
"{src,test}/**/*.js": ["eslint --fix", "prettier --write"]
"{src,test}/**/*.js": [
"eslint --fix",
"prettier --write"
]
}
}
9 changes: 7 additions & 2 deletions src/formLeaveGuardMixin.js
Expand Up @@ -48,6 +48,7 @@ const FormLeaveGuardMixin = (formKeys, options = {}) => {
);
if (areAllClean) return next();
} else {
/* istanbul ignore next - Uncommon case */
return next();
}

Expand All @@ -70,15 +71,19 @@ const FormLeaveGuardMixin = (formKeys, options = {}) => {

if (shouldContinue) {
// Reset the form before leaving (otherwise it sometimes retains data)
formKeys.forEach((key) => resetForm.call(this, key));
if (Array.isArray(formKeys)) {
formKeys.forEach((key) => resetForm.call(this, key));
} else {
resetForm.call(this, formKeys);
}

return next();
}
};

// Set the callback and pass the reference to the "onPrevent" callback
this[callbackKey] = callback;
onPrevent(callback);
onPrevent && onPrevent(callback);
},
};
};
Expand Down
36 changes: 18 additions & 18 deletions test/formCreateMixin.test.js
Expand Up @@ -6,6 +6,9 @@ import { createForm, FormCreateMixin } from "../src";

describe("Form Create Mixin", () => {
let wrapper = null;
const Component = Vue.component("formComponent", {
template: "<div />",
});

const formName = "form";
const fields = {
Expand All @@ -22,10 +25,6 @@ describe("Form Create Mixin", () => {

// Setup the component with mixin before each test
const beforeHandler = () => {
const Component = Vue.component("formComponent", {
template: "<div />",
});

wrapper = shallowMount(Component, {
mixins: [
// NOTE: Must spread setup values to avoid mutating by reference!
Expand All @@ -34,11 +33,12 @@ describe("Form Create Mixin", () => {
});
};

beforeEach(beforeHandler);

afterEach(() => {
const afterHandler = () => {
wrapper.destroy();
});
};

beforeEach(beforeHandler);
afterEach(afterHandler);

it("should run mixin in component", () => {
// Should import successfully
Expand All @@ -64,7 +64,8 @@ describe("Form Create Mixin", () => {
});

describe("should handle form data methods", () => {
beforeAll(beforeHandler);
beforeEach(beforeHandler);
afterEach(afterHandler);

it("'should get form values", () => {
expect(wrapper.vm[formName].getValues()).toEqual(fields);
Expand Down Expand Up @@ -96,7 +97,8 @@ describe("Form Create Mixin", () => {
});

describe("should handle form flag methods", () => {
beforeAll(beforeHandler);
beforeEach(beforeHandler);
afterEach(afterHandler);

it("should not set invalid flags", () => {
wrapper.vm[formName].setFlag("locked", true);
Expand All @@ -121,7 +123,8 @@ describe("Form Create Mixin", () => {
});

describe("should handle form computed flags", () => {
beforeAll(beforeHandler);
beforeEach(beforeHandler);
afterEach(afterHandler);

it("should calculate computed 'changed' flag", () => {
// Should start unchanged
Expand All @@ -147,19 +150,17 @@ describe("Form Create Mixin", () => {
let wrapperStatic = null;

beforeEach(() => {
// NOTE: Create separate Vue instance to test these options (behavior changes)
const ComponentStatic = Vue.component("staticFormComponent", {
template: "<div />",
});

const options = { calculateChanged: false, flags: { locked: true } };
wrapperStatic = shallowMount(ComponentStatic, {
wrapperStatic = shallowMount(Component, {
mixins: [
// NOTE: Must spread setup values to avoid mutating by reference!
FormCreateMixin(formName, { ...fields }, options),
],
});
});
afterEach(() => {
wrapperStatic.destroy();
});

it("should use custom flags", () => {
// Should have custom flag set by mixin
Expand Down Expand Up @@ -205,7 +206,6 @@ describe("Form Create Function", () => {
};

beforeEach(beforeHandler);

afterEach(() => {
wrapper.destroy();
});
Expand Down
175 changes: 172 additions & 3 deletions test/formLeaveGuardMixin.test.js
@@ -1,9 +1,178 @@
import Vue from "vue";
import { shallowMount } from "@vue/test-utils";

// Utilities
import { FormLeaveGuardMixin } from "../src";
import { FormCreateMixin, FormLeaveGuardMixin } from "../src";

const nextFn = jest.fn();
const onPreventFn = jest.fn();
const Component = Vue.component("formComponent", {
template: "<div />",
});

/**
* Shallow mount a component with the mixin (and custom options)
* @param {string} formKey - Form key
* @param {Object} mixinOptions - Mixin options
* @return - Vue wrapper
*/
const mountComponent = (formKey, mixinOptions = {}) =>
shallowMount(Component, {
mixins: [
// NOTE: Must spread setup values to avoid mutating by reference!
FormCreateMixin(formName, { ...fields }),
FormLeaveGuardMixin(formKey, {
activeKey,
callbackKey,
onPrevent: onPreventFn,
...mixinOptions,
}),
],
});

const formName = "form";
const fields = {
email: "test@example.com",
password: "******",
};
const fieldChanges = {
email: "noone@example.com",
};

const activeKey = "isLeavingForm";
const callbackKey = "onFormLeave";

describe("Form Leave Guard Mixin", () => {
it("should import mixin", () => {
// Assert
let wrapper = null;
let beforeRouteLeave = null;

// Setup the component with mixin before each test
const beforeHandler = () => {
wrapper = mountComponent(formName);
({ beforeRouteLeave } = wrapper.vm.$options);
};

const afterHandler = () => {
wrapper.destroy();
nextFn.mockReset();
onPreventFn.mockReset();
};

beforeEach(beforeHandler);
afterEach(afterHandler);

it("should run mixin in component", () => {
// Should import successfully
expect(FormLeaveGuardMixin).toBeTruthy();
// Should have active key from mixin options
expect(wrapper.vm[activeKey]).toBe(false);
// Should have callback key from mixin options
expect(wrapper.vm[callbackKey]).toBeNull();
});

it("should handle leaving clean form", () => {
beforeRouteLeave.call(wrapper.vm, "toObj", "fromObj", nextFn);

// Should call "next" when leaving clean form
expect(nextFn).toHaveBeenCalled();
// Should not set active key when leaving clean form
expect(wrapper.vm[activeKey]).toBe(false);
// Should not set callback handler when leaving clean form
expect(wrapper.vm[callbackKey]).toBeNull();
// Should not call custom prevent handler when leaving clean form
expect(onPreventFn).not.toHaveBeenCalled();
});

it("should handle leaving clean forms (multiple)", () => {
const wrapperMulti = mountComponent([formName]);
const beforeRouteLeaveMulti = wrapperMulti.vm.$options.beforeRouteLeave;
beforeRouteLeaveMulti.call(wrapperMulti.vm, "toObj", "fromObj", nextFn);

// Should call "next" when leaving clean forms
expect(nextFn).toHaveBeenCalled();
// Should not set active key when leaving clean forms
expect(wrapperMulti.vm[activeKey]).toBe(false);
// Should not set callback handler when leaving clean forms
expect(wrapperMulti.vm[callbackKey]).toBeNull();
// Should not call custom prevent handler when leaving clean forms
expect(onPreventFn).not.toHaveBeenCalled();
});

describe("should handle leaving dirty form", () => {
beforeEach(() => {
beforeHandler();

// Make changes to form (to trigger "changed" flag)
wrapper.vm[formName].setValues({ ...fieldChanges }, false);
beforeRouteLeave.call(wrapper.vm, "toObj", "fromObj", nextFn);
});
afterEach(afterHandler);

it("should prevent leaving dirty form", async () => {
// Should not call "next" when leaving dirty form
expect(nextFn).not.toHaveBeenCalled();
// Should set "is leaving" active getter
expect(wrapper.vm[activeKey]).toBe(true);
// Should set form leave confirmation callback
expect(wrapper.vm[callbackKey]).not.toBeNull();

// Should remove callback after it is the setter is called (v-model issue)
wrapper.vm[activeKey] = false;
expect(wrapper.vm[callbackKey]).not.toBeNull();
await Vue.nextTick();
expect(wrapper.vm[callbackKey]).toBeNull();
});

it("should remain on dirty form after cancellation", () => {
wrapper.vm[callbackKey]();

// Should reset callback key when staying on dirty form
expect(wrapper.vm[callbackKey]).toBeNull();
// Should not call "next" when staying on dirty form
expect(nextFn).not.toHaveBeenCalled();
});

it("should leave dirty form after confirmation", () => {
wrapper.vm[callbackKey](true);

// Should reset callback key when leaving dirty form
expect(wrapper.vm[callbackKey]).toBeNull();
// Should call "next" when leaving dirty form
expect(nextFn).toHaveBeenCalled();
// Should reset form state when leaving dirty form
expect(wrapper.vm[formName].getValues()).toEqual(fields);
});

it("should leave dirty forms (multiple) after confirmation", () => {
const wrapperMulti = mountComponent([formName]);
const beforeRouteLeaveMulti = wrapperMulti.vm.$options.beforeRouteLeave;
wrapperMulti.vm[formName].setValues({ ...fieldChanges }, false);
beforeRouteLeaveMulti.call(wrapperMulti.vm, "toObj", "fromObj", nextFn);
wrapperMulti.vm[callbackKey](true);

// Should reset callback key when leaving dirty form
expect(wrapperMulti.vm[callbackKey]).toBeNull();
// Should call "next" when leaving dirty form
expect(nextFn).toHaveBeenCalled();
// Should reset form state when leaving dirty form
expect(wrapperMulti.vm[formName].getValues()).toEqual(fields);
});
});

it("should prevent leaving dirty form (without callbacks) if specified", () => {
const wrapperPrevent = mountComponent(formName, {
onlyPrevent: true,
});
const beforeRouteLeavePrevent = wrapperPrevent.vm.$options.beforeRouteLeave;

wrapperPrevent.vm[formName].setValues({ ...fieldChanges }, false);
beforeRouteLeavePrevent.call(wrapperPrevent.vm, "toObj", "fromObj", nextFn);

// Should not call "next" when only preventing leaving dirty form
expect(nextFn).not.toBeCalled();
// Should not set callback when only preventing leaving dirty form
expect(wrapperPrevent.vm[callbackKey]).toBeNull();
// Should call custom prevent handler when only preventing leaving dirty form
expect(onPreventFn).toBeCalled();
});
});

0 comments on commit 2f9607d

Please sign in to comment.