diff --git a/src/components/__tests__/__snapshots__/checker.js.snap b/src/components/__tests__/__snapshots__/checker.js.snap index 1d3253c..fa6ee37 100644 --- a/src/components/__tests__/__snapshots__/checker.js.snap +++ b/src/components/__tests__/__snapshots__/checker.js.snap @@ -235,7 +235,6 @@ exports[`render matches snapshot with errors 1`] = ` > { describe("fixIssue", () => { let ev, error - beforeEach(async () => { - await promisify(instance.check.bind(instance))() - component.update() - error = instance.state.errors[0] - ev = { preventDefault: jest.fn() } - jest.spyOn(instance, "check") - }) + describe("_fixSingleError", () => { + beforeEach(async () => { + await promisify(instance.check.bind(instance))() + component.update() + error = instance.state.errors[0] + ev = { preventDefault: jest.fn() } + jest.spyOn(instance, "check") + }) - test("updates the real node", () => { - instance.fixIssue(ev) - const formState = instance.state.formState - expect(error.rule.update).toHaveBeenCalledWith(error.node, formState) - }) + test("updates the real node", () => { + instance.fixIssue(ev) + const formState = instance.state.formState + expect(error.rule.update).toHaveBeenCalledWith(error.node, formState) + }) - test("checks everything after applying a fix", () => { - instance.fixIssue(ev) - expect(instance.check).toHaveBeenCalled() - }) + test("checks everything after applying a fix", () => { + instance.fixIssue(ev) + expect(instance.check).toHaveBeenCalled() + }) + + test("does nothing if there are no errors", () => { + instance.state.errors = [] + instance.fixIssue(ev) + expect(instance.check).not.toHaveBeenCalled() + }) - test("does nothing if there are no errors", () => { - instance.state.errors = [] - instance.fixIssue(ev) - expect(instance.check).not.toHaveBeenCalled() + test("focuses the close button", () => { + instance._closeButtonRef = { focus: jest.fn() } + instance.fixIssue(ev) + expect(instance._closeButtonRef.focus).toHaveBeenCalled() + }) }) - test("focuses the close button", () => { - instance._closeButtonRef = { focus: jest.fn() } - instance.fixIssue(ev) - expect(instance._closeButtonRef.focus).toHaveBeenCalled() + describe("_fixBulkError", () => { + beforeEach(async () => { + node.appendChild(document.createElement("div")) + + await promisify(instance.check.bind(instance))() + component.update() + error = instance.state.errors[0] + ev = { preventDefault: jest.fn() } + jest.spyOn(instance, "check") + + const newErrors = instance.state.errors + newErrors.forEach(err => (err.rule.bulkUpdateSupported = true)) + component.setState({ errors: newErrors, formStateBulkUpdateEnabled: true }) + }) + + test("updates the real nodes", () => { + const [error1, error2, error3] = instance.state.errors + instance.fixIssue(ev) + const formState = instance.state.formState + expect(error1.rule.update).toHaveBeenCalledWith(error1.node, formState) + expect(error2.rule.update).toHaveBeenCalledWith(error2.node, formState) + expect(error3.rule.update).toHaveBeenCalledWith(error3.node, formState) + }) + + test("does not update the other error node", async () => { + const newErrors = instance.state.errors + newErrors[2].rule = {...newErrors[0].rule, id: 'another-error', update: jest.fn()} + component.setState({ errors: newErrors, formStateBulkUpdateEnabled: true }) + const [error1, error2, error3] = instance.state.errors + instance.fixIssue(ev) + const formState = instance.state.formState + expect(error1.rule.update).toHaveBeenCalledWith(error1.node, formState) + expect(error2.rule.update).toHaveBeenCalledWith(error2.node, formState) + expect(error3.rule.update).not.toHaveBeenCalled() + }) + + test("checks everything after applying a fix", () => { + instance.fixIssue(ev) + expect(instance.check).toHaveBeenCalled() + }) + + test("does nothing if there are no errors", () => { + instance.state.errors = [] + instance.fixIssue(ev) + expect(instance.check).not.toHaveBeenCalled() + }) + + test("focuses the close button", () => { + instance._closeButtonRef = { focus: jest.fn() } + instance.fixIssue(ev) + expect(instance._closeButtonRef.focus).toHaveBeenCalled() + }) }) }) diff --git a/src/components/checker.js b/src/components/checker.js index 5152857..805a166 100644 --- a/src/components/checker.js +++ b/src/components/checker.js @@ -42,6 +42,7 @@ export default class Checker extends React.Component { errors: [], formState: {}, formStateValid: false, + formStateBulkUpdateEnabled: false, errorIndex: 0, config: {}, showWhyPopover: false, @@ -206,6 +207,7 @@ export default class Checker extends React.Component { this.setState({ formState, formStateValid: this.formStateValid(formState), + formStateBulkUpdateEnabled: false, }) } @@ -224,7 +226,7 @@ export default class Checker extends React.Component { return rule.test(node) } - fixIssue() { + _fixNode(doneCallback) { const rule = this.errorRule() let node = this.errorNode() if (rule && node) { @@ -234,8 +236,44 @@ export default class Checker extends React.Component { if (this._closeButtonRef) { this._closeButtonRef.focus() } - const errorIndex = this.state.errorIndex - this.check(() => this.setErrorIndex(errorIndex)) + this.check(doneCallback) + } + } + + _findMatchErrorIndexes(ruleId) { + return this.state.errors.reduce((result, {rule}, index) => { + if (rule.id === ruleId) { + result.push(index) + } + return result + }, []) + } + + _fixSingleError() { + const errorIndex = this.state.errorIndex + this._fixNode(() => this.setErrorIndex(errorIndex)) + } + + _fixBulkError() { + const fixMatchedErrors = (ruleId) => { + const indexes = this._findMatchErrorIndexes(ruleId) + if (indexes.length === 0) { + return + } + const errorIndex = indexes[0] + this.setErrorIndex(errorIndex) + this._fixNode(fixMatchedErrors) + } + const rule = this.errorRule() + this._fixNode(() => fixMatchedErrors(rule.id)) + } + + fixIssue() { + const rule = this.errorRule() + if (this.state.formStateBulkUpdateEnabled && rule?.bulkUpdateSupported) { + this._fixBulkError() + } else { + this._fixSingleError() } } @@ -295,6 +333,8 @@ export default class Checker extends React.Component { num: this.state.errorIndex + 1, total: this.state.errors.length, }) + const showBulkUpdate = rule && rule.bulkUpdateSupported && this._findMatchErrorIndexes(rule.id).length > 1 && + this.state.formStateValid return ( @@ -410,6 +450,17 @@ export default class Checker extends React.Component { {this.renderField(f)} ))} + { + showBulkUpdate && + + this.setState({formStateBulkUpdateEnabled: e.target.checked})} + /> + + } { export default { id: "headings-sequence", + bulkUpdateSupported: true, test: elem => { const testTags = { H2: true,