diff --git a/src/editors/containers/TextEditor/components/ImageSettingsModal/DimensionControls.jsx b/src/editors/containers/TextEditor/components/ImageSettingsModal/DimensionControls.jsx
index ed0b6af87..bcaf2bb1b 100644
--- a/src/editors/containers/TextEditor/components/ImageSettingsModal/DimensionControls.jsx
+++ b/src/editors/containers/TextEditor/components/ImageSettingsModal/DimensionControls.jsx
@@ -14,7 +14,7 @@ import hooks from './hooks';
/**
* Wrapper for image dimension inputs and the lock checkbox.
- * @param {obj} locked - locked dimension object
+ * @param {bool} isLocked - are dimensions locked
* @param {func} lock - lock dimensions
* @param {func} setHeight - updates dimensions based on new height
* @param {func} setWidth - updates dimensions based on new width
@@ -23,7 +23,7 @@ import hooks from './hooks';
* @param {obj} value - local dimension values { height, width }
*/
export const DimensionControls = ({
- locked,
+ isLocked,
lock,
setHeight,
setWidth,
@@ -54,16 +54,15 @@ export const DimensionControls = ({
/>
));
DimensionControls.defaultProps = {
- locked: null,
value: {
height: 100,
width: 100,
@@ -76,10 +75,7 @@ DimensionControls.propTypes = ({
}),
setHeight: PropTypes.func.isRequired,
setWidth: PropTypes.func.isRequired,
- locked: PropTypes.shape({
- width: PropTypes.number,
- height: PropTypes.number,
- }),
+ isLocked: PropTypes.bool.isRequired,
lock: PropTypes.func.isRequired,
unlock: PropTypes.func.isRequired,
updateDimensions: PropTypes.func.isRequired,
diff --git a/src/editors/containers/TextEditor/components/ImageSettingsModal/DimensionControls.test.jsx b/src/editors/containers/TextEditor/components/ImageSettingsModal/DimensionControls.test.jsx
index 8c3a0ed5b..fe83c77e3 100644
--- a/src/editors/containers/TextEditor/components/ImageSettingsModal/DimensionControls.test.jsx
+++ b/src/editors/containers/TextEditor/components/ImageSettingsModal/DimensionControls.test.jsx
@@ -8,7 +8,9 @@ jest.mock('./hooks', () => ({
describe('DimensionControls', () => {
const props = {
+ lockDims: { width: 12, height: 15 },
locked: { 'props.locked': 'lockedValue' },
+ isLocked: true,
value: { width: 20, height: 40 },
};
beforeEach(() => {
@@ -27,5 +29,9 @@ describe('DimensionControls', () => {
expect(el).toMatchSnapshot();
expect(el.isEmptyRender()).toEqual(true);
});
+ test('unlocked dimensions', () => {
+ const el = shallow();
+ expect(el).toMatchSnapshot();
+ });
});
});
diff --git a/src/editors/containers/TextEditor/components/ImageSettingsModal/__snapshots__/DimensionControls.test.jsx.snap b/src/editors/containers/TextEditor/components/ImageSettingsModal/__snapshots__/DimensionControls.test.jsx.snap
index c1ef476fb..a459a58e2 100644
--- a/src/editors/containers/TextEditor/components/ImageSettingsModal/__snapshots__/DimensionControls.test.jsx.snap
+++ b/src/editors/containers/TextEditor/components/ImageSettingsModal/__snapshots__/DimensionControls.test.jsx.snap
@@ -48,3 +48,50 @@ exports[`DimensionControls render snapshot 1`] = `
`;
+
+exports[`DimensionControls render unlocked dimensions 1`] = `
+
+
+ Image Dimensions
+
+
+
+
+
+
+
+`;
diff --git a/src/editors/containers/TextEditor/components/ImageSettingsModal/hooks.js b/src/editors/containers/TextEditor/components/ImageSettingsModal/hooks.js
index c71ba5b3a..2dea22449 100644
--- a/src/editors/containers/TextEditor/components/ImageSettingsModal/hooks.js
+++ b/src/editors/containers/TextEditor/components/ImageSettingsModal/hooks.js
@@ -5,12 +5,13 @@ import * as module from './hooks';
// Simple wrappers for useState to allow easy mocking for tests.
export const state = {
- dimensions: (val) => React.useState(val),
- locked: (val) => React.useState(val),
- local: (val) => React.useState(val),
- lockInitialized: (val) => React.useState(val),
altText: (val) => React.useState(val),
+ dimensions: (val) => React.useState(val),
isDecorative: (val) => React.useState(val),
+ isLocked: (val) => React.useState(val),
+ local: (val) => React.useState(val),
+ lockDims: (val) => React.useState(val),
+ lockInitialized: (val) => React.useState(val),
};
export const dimKeys = StrictDict({
@@ -39,12 +40,15 @@ const checkEqual = (d1, d2) => (d1.height === d2.height && d1.width === d2.width
export const getValidDimensions = ({
dimensions,
local,
- locked,
+ isLocked,
+ lockDims,
}) => {
+ if (!isLocked || checkEqual(local, dimensions)) {
+ return local;
+ }
const out = {};
let iter;
- const { minInc } = locked;
- const isMin = dimensions.height === minInc.height;
+ const isMin = dimensions.height === lockDims.height;
const keys = (local.height !== dimensions.height)
? { changed: dimKeys.height, other: dimKeys.width }
@@ -55,51 +59,16 @@ export const getValidDimensions = ({
// don't move down if already at minimum size
if (direction < 0 && isMin) { return dimensions; }
// find closest valid iteration of the changed field
- iter = Math.max(Math.round(local[keys.changed] / minInc[keys.changed]), 1);
+ iter = Math.max(Math.round(local[keys.changed] / lockDims[keys.changed]), 1);
// if closest valid iteration is current iteration, move one iteration in the change direction
- if (iter === (dimensions[keys.changed] / minInc[keys.changed])) { iter += direction; }
+ if (iter === (dimensions[keys.changed] / lockDims[keys.changed])) { iter += direction; }
- out[keys.changed] = Math.round(iter * minInc[keys.changed]);
- out[keys.other] = Math.round(out[keys.changed] * (locked[keys.other] / locked[keys.changed]));
+ out[keys.changed] = Math.round(iter * lockDims[keys.changed]);
+ out[keys.other] = Math.round(out[keys.changed] * (lockDims[keys.other] / lockDims[keys.changed]));
return out;
};
-/**
- * newDimensions({ dimensions, local, locked })
- * Returns the local dimensions if unlocked or unchanged, and otherwise returns new valid
- * dimensions.
- * @param {obj} dimensions - current stored dimensions
- * @param {obj} local - local (active) dimensions in the inputs
- * @param {obj} locked - locked dimensions
- * @return {obj} - output dimensions after attempted move ({ height, width })
- */
-export const newDimensions = ({ dimensions, local, locked }) => (
- (!locked || checkEqual(local, dimensions))
- ? local
- : module.getValidDimensions({ dimensions, local, locked })
-);
-
-/**
- * lockDimensions({ dimensions, lockInitialized, setLocked })
- * Lock dimensions if lock initialized. Store minimum valid increment on lock so
- * that we don't have re-compute.
- * @param {obj} dimensions - current stored dimensions
- * @param {bool} lockInitialized - has the lock state initialized?
- * @param {func} setLocked - set lock state
- */
-export const lockDimensions = ({ dimensions, lockInitialized, setLocked }) => {
- if (!lockInitialized) { return; }
-
- // find minimum viable increment
- let gcd = findGcd(dimensions.width, dimensions.height);
- if ([dimensions.width, dimensions.height].some(v => !Number.isInteger(v / gcd))) {
- gcd = 1;
- }
- const minInc = { width: dimensions.width / gcd, height: dimensions.height / gcd, gcd };
- setLocked({ ...dimensions, minInc });
-};
-
/**
* dimensionLockHooks({ dimensions })
* Returns a set of hooks pertaining to the dimension locks.
@@ -107,22 +76,30 @@ export const lockDimensions = ({ dimensions, lockInitialized, setLocked }) => {
* @param {obj} dimensions - current stored dimensions
* @return {obj} - dimension lock hooks
* {func} initializeLock - enable the lock mechanism
- * {obj} locked - current locked state
- * {func} lock - lock the current dimensions
+ * {bool} isLocked - are dimensions locked?
+ * {obj} lockDims - image dimensions ({ height, width })
+ * {func} lock - lock the dimensions
* {func} unlock - unlock the dimensions
*/
-export const dimensionLockHooks = ({ dimensions }) => {
- const [locked, setLocked] = module.state.locked(null);
- const [lockInitialized, setLockInitialized] = module.state.lockInitialized(null);
- const lock = () => module.lockDimensions({ lockInitialized, dimensions, setLocked });
+export const dimensionLockHooks = () => {
+ const [lockDims, setLockDims] = module.state.lockDims(null);
+ const [isLocked, setIsLocked] = module.state.isLocked(true);
- React.useEffect(lock, [lockInitialized]);
+ const initializeLock = ({ width, height }) => {
+ // find minimum viable increment
+ let gcd = findGcd(width, height);
+ if ([width, height].some(v => !Number.isInteger(v / gcd))) {
+ gcd = 1;
+ }
+ setLockDims({ width: width / gcd, height: height / gcd });
+ };
return {
- initializeLock: () => setLockInitialized(true),
- locked,
- lock,
- unlock: () => setLocked(null),
+ initializeLock,
+ isLocked,
+ lock: () => setIsLocked(true),
+ lockDims,
+ unlock: () => setIsLocked(false),
};
};
@@ -146,31 +123,36 @@ export const dimensionLockHooks = ({ dimensions }) => {
export const dimensionHooks = () => {
const [dimensions, setDimensions] = module.state.dimensions(null);
const [local, setLocal] = module.state.local(null);
- const setAll = (value) => {
- setDimensions(value);
- setLocal(value);
+ const setAll = ({ height, width }) => {
+ setDimensions({ height, width });
+ setLocal({ height, width });
};
const {
initializeLock,
+ isLocked,
lock,
- locked,
+ lockDims,
unlock,
} = module.dimensionLockHooks({ dimensions });
+
return {
onImgLoad: (selection) => ({ target: img }) => {
- setAll({
- height: selection.height || img.naturalHeight,
- width: selection.width || img.naturalWidth,
- });
- initializeLock();
+ const imageDims = { height: img.naturalHeight, width: img.naturalWidth };
+ setAll(selection.height ? selection : imageDims);
+ initializeLock(imageDims);
},
- locked,
+ isLocked,
lock,
unlock,
value: local,
setHeight: (height) => setLocal({ ...local, height: parseInt(height, 10) }),
setWidth: (width) => setLocal({ ...local, width: parseInt(width, 10) }),
- updateDimensions: () => setAll(module.newDimensions({ dimensions, local, locked })),
+ updateDimensions: () => setAll(module.getValidDimensions({
+ dimensions,
+ local,
+ isLocked,
+ lockDims,
+ })),
};
};
diff --git a/src/editors/containers/TextEditor/components/ImageSettingsModal/hooks.test.js b/src/editors/containers/TextEditor/components/ImageSettingsModal/hooks.test.js
index 478258a74..43e6a2f65 100644
--- a/src/editors/containers/TextEditor/components/ImageSettingsModal/hooks.test.js
+++ b/src/editors/containers/TextEditor/components/ImageSettingsModal/hooks.test.js
@@ -1,3 +1,4 @@
+import React from 'react';
import { StrictDict } from '../../../../utils';
import { MockUseState } from '../../../../../testUtils';
import * as hooks from './hooks';
@@ -5,6 +6,7 @@ import * as hooks from './hooks';
jest.mock('react', () => ({
...jest.requireActual('react'),
useEffect: jest.fn(),
+ useState: (val) => ({ useState: val }),
}));
const simpleDims = { width: 3, height: 4 };
@@ -24,29 +26,69 @@ const hookKeys = StrictDict(Object.keys(hooks).reduce(
let hook;
+const testVal = 'MY test VALUE';
+
+describe('state values', () => {
+ const testStateMethod = (key) => {
+ expect(hooks.state[key](testVal)).toEqual(React.useState(testVal));
+ };
+ test('provides altText state value', () => testStateMethod(state.keys.altText));
+ test('provides dimensions state value', () => testStateMethod(state.keys.dimensions));
+ test('provides isDecorative state value', () => testStateMethod(state.keys.isDecorative));
+ test('provides isLocked state value', () => testStateMethod(state.keys.isLocked));
+ test('provides local state value', () => testStateMethod(state.keys.local));
+ test('provides lockDims state value', () => testStateMethod(state.keys.lockDims));
+ test('provides lockInitialized state value', () => testStateMethod(state.keys.lockInitialized));
+});
+
describe('ImageSettingsModal hooks', () => {
describe('dimensions-related hooks', () => {
describe('getValidDimensions', () => {
+ it('returns local dimensions if not locked', () => {
+ expect(hooks.getValidDimensions({
+ dimensions: simpleDims,
+ local: reducedDims,
+ isLocked: false,
+ lockDims: simpleDims,
+ })).toEqual(reducedDims);
+ });
+ it('returns local dimensions if the same as stored', () => {
+ expect(hooks.getValidDimensions({
+ dimensions: simpleDims,
+ local: simpleDims,
+ isLocked: true,
+ lockDims: reducedDims,
+ })).toEqual(simpleDims);
+ });
describe('decreasing change when at minimum valid increment', () => {
it('returns current dimensions', () => {
const dimensions = { ...reducedDims };
- const locked = { minInc: { ...dimensions, gcd } };
+ const lockDims = { ...dimensions };
let local = { ...dimensions, width: dimensions.width - 1 };
expect(
- hooks.getValidDimensions({ dimensions, local, locked }),
+ hooks.getValidDimensions({
+ dimensions,
+ isLocked: true,
+ local,
+ lockDims,
+ }),
).toEqual(dimensions);
local = { ...dimensions, height: dimensions.height - 1 };
expect(
- hooks.getValidDimensions({ dimensions, local, locked }),
+ hooks.getValidDimensions({
+ dimensions,
+ isLocked: true,
+ local,
+ lockDims,
+ }),
).toEqual(dimensions);
});
});
describe('valid change', () => {
it(
- 'returns the nearest valid pair of dimensions in the change direciton',
+ 'returns the nearest valid pair of dimensions in the change direction',
() => {
- const w = 7;
- const h = 13;
+ const [w, h] = [7, 13];
const values = [
// bumps up if direction is up but nearest is current
[[w + 1, h], [w * 2, h * 2]],
@@ -66,75 +108,14 @@ describe('ImageSettingsModal hooks', () => {
expect(hooks.getValidDimensions({
dimensions,
local: { width: local[0], height: local[1] },
- locked: { ...dimensions, minInc: { ...dimensions, gcd: 1 } },
+ lockDims: { ...dimensions },
+ isLocked: true,
})).toEqual({ width: expected[0], height: expected[1] });
});
},
);
});
});
- describe('newDimensions', () => {
- it('returns the local values if not locked, or if local is equal to dimensions', () => {
- expect(hooks.newDimensions({
- dimensions: { ...simpleDims },
- local: { ...simpleDims },
- locked: { ...simpleDims },
- })).toEqual({ ...simpleDims });
- expect(hooks.newDimensions({
- dimensions: { ...simpleDims },
- local: { ...reducedDims },
- locked: null,
- })).toEqual({ ...reducedDims });
- });
- it('returns getValidDimensions if locked and local has changed', () => {
- const getValidDimensions = (args) => ({ getValidDimensions: args });
- jest.spyOn(hooks, hookKeys.getValidDimensions).mockImplementationOnce(getValidDimensions);
- const args = {
- dimensions: { ...simpleDims },
- local: { ...multiDims },
- locked: { ...reducedDims },
- };
- expect(hooks.newDimensions(args)).toEqual(getValidDimensions(args));
- });
- });
- describe('lockDimensions', () => {
- it('does not call setLocked if lockInitialized is false', () => {
- state.setState.locked = jest.fn();
- hooks.lockDimensions({
- dimensions: simpleDims,
- setLocked: state.setState.locked,
- lockInitialized: false,
- });
- expect(state.setState.locked).not.toHaveBeenCalled();
- });
- it(
- 'calls setLocked with the given dimensions and minInc, including gcd',
- () => {
- state.setState.locked = jest.fn();
- hooks.lockDimensions({
- dimensions: simpleDims,
- setLocked: state.setState.locked,
- lockInitialized: true,
- });
- expect(state.setState.locked).toHaveBeenCalledWith({
- ...simpleDims,
- minInc: { gcd: 1, ...simpleDims },
- });
- state.setState.locked.mockClear();
-
- hooks.lockDimensions({
- dimensions: multiDims,
- setLocked: state.setState.locked,
- lockInitialized: true,
- });
- expect(hooks.findGcd(multiDims.width, multiDims.height)).toEqual(7);
- expect(state.setState.locked).toHaveBeenCalledWith({
- ...multiDims,
- minInc: { gcd, ...reducedDims },
- });
- },
- );
- });
describe('dimensionLockHooks', () => {
beforeEach(() => {
state.mock();
@@ -143,29 +124,27 @@ describe('ImageSettingsModal hooks', () => {
afterEach(() => {
state.restore();
});
-
- test('locked is initially null', () => {
- expect(hook.locked).toEqual(null);
+ test('lockDims defaults to null', () => {
+ expect(hook.lockDims).toEqual(null);
});
- test('initializeLock calls setLockInitialized with true', () => {
- hook.initializeLock();
- expect(state.setState.lockInitialized).toHaveBeenCalledWith(true);
+ test('isLocked defaults to true', () => {
+ expect(hook.isLocked).toEqual(true);
});
- test('lock calls lockDimensions with lockInitialized, dimensions, and setLocked', () => {
- state.mockVal(state.keys.lockInitialized, true, state.setState.lockInitialized);
- hook = hooks.dimensionLockHooks({ dimensions: simpleDims });
- const lockDimensionsSpy = jest.spyOn(hooks, hookKeys.lockDimensions);
- hook.lock();
- expect(lockDimensionsSpy).toHaveBeenCalledWith({
- dimensions: simpleDims,
- setLocked: state.setState.locked,
- lockInitialized: true,
+ describe('initializeLock', () => {
+ it('calls setLockDims with the passed dimensions divided by their gcd', () => {
+ hook.initializeLock(multiDims);
+ expect(state.setState.lockDims).toHaveBeenCalledWith(reducedDims);
});
});
+ test('lock sets isLocked to true', () => {
+ hook = hooks.dimensionLockHooks({ dimensions: simpleDims });
+ hook.lock();
+ expect(state.setState.isLocked).toHaveBeenCalledWith(true);
+ });
test('unlock sets locked to null', () => {
hook = hooks.dimensionLockHooks({ dimensions: simpleDims });
hook.unlock();
- expect(state.setState.locked).toHaveBeenCalledWith(null);
+ expect(state.setState.isLocked).toHaveBeenCalledWith(false);
});
});
describe('dimensionHooks', () => {
@@ -230,17 +209,21 @@ describe('ImageSettingsModal hooks', () => {
});
describe('updateDimensions', () => {
it('sets local and stored dimensions to newDimensions output', () => {
- const newDimensions = (args) => ({ newDimensions: args });
- state.mockVal(state.keys.dimensions, simpleDims, state.setState.dimensions);
- state.mockVal(state.keys.locked, reducedDims, state.setState.locked);
- state.mockVal(state.keys.local, multiDims, state.setState.local);
- jest.spyOn(hooks, hookKeys.newDimensions).mockImplementationOnce(newDimensions);
+ // store values we care about under height or width, and add junk data to be stripped out.
+ const testDims = (args) => ({ ...simpleDims, height: args });
+ const getValidDimensions = (args) => ({ ...testDims(args), junk: 'data' });
+ state.mockVal(state.keys.isLocked, true);
+ state.mockVal(state.keys.dimensions, simpleDims);
+ state.mockVal(state.keys.lockDims, reducedDims);
+ state.mockVal(state.keys.local, multiDims);
+ jest.spyOn(hooks, hookKeys.getValidDimensions).mockImplementationOnce(getValidDimensions);
hook = hooks.dimensionHooks();
hook.updateDimensions();
- const expected = newDimensions({
+ const expected = testDims({
dimensions: simpleDims,
- locked: reducedDims,
+ lockDims: reducedDims,
local: multiDims,
+ isLocked: true,
});
expect(state.setState.local).toHaveBeenCalledWith(expected);
expect(state.setState.dimensions).toHaveBeenCalledWith(expected);