зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1748222 - [devtools] Remove debugger's Resource API. r=bomsy
This has been mostly replaced by Redux's createSelector and its `resultEqualityCheck` argument. Differential Revision: https://phabricator.services.mozilla.com/D142544
This commit is contained in:
Родитель
02c69d75ac
Коммит
2607e2017e
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import { createSelector } from "reselect";
|
||||
import { shallowEqual } from "../utils/resource";
|
||||
import { shallowEqual } from "../utils/shallow-equal";
|
||||
|
||||
import {
|
||||
getPrettySourceURL,
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import { createSelector } from "reselect";
|
||||
import { shallowEqual } from "../utils/resource";
|
||||
import { shallowEqual } from "../utils/shallow-equal";
|
||||
import { getPrettySourceURL } from "../utils/source";
|
||||
|
||||
import { getSource, getSpecificSourceByURL, getSourcesMap } from "./sources";
|
||||
|
|
|
@ -7,7 +7,6 @@ DIRS += [
|
|||
"breakpoint",
|
||||
"editor",
|
||||
"pause",
|
||||
"resource",
|
||||
"sources-tree",
|
||||
]
|
||||
|
||||
|
@ -40,6 +39,7 @@ CompiledModules(
|
|||
"quick-open.js",
|
||||
"result-list.js",
|
||||
"selected-location.js",
|
||||
"shallow-equal.js",
|
||||
"source-maps.js",
|
||||
"source-queue.js",
|
||||
"source.js",
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import { getResourceValues, getValidatedResource, makeIdentity } from "./core";
|
||||
import { arrayShallowEqual } from "./compare";
|
||||
|
||||
export function makeMapWithArgs(map) {
|
||||
const wrapper = (resource, identity, args) => map(resource, identity, args);
|
||||
wrapper.needsArgs = true;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
export function makeResourceQuery({
|
||||
cache,
|
||||
filter,
|
||||
map,
|
||||
reduce,
|
||||
resultCompare,
|
||||
}) {
|
||||
const loadResource = makeResourceMapper(map);
|
||||
|
||||
return cache((state, context, existing) => {
|
||||
const ids = filter(getResourceValues(state), context.args);
|
||||
const mapped = ids.map(id => loadResource(state, id, context));
|
||||
|
||||
if (existing && arrayShallowEqual(existing.mapped, mapped)) {
|
||||
// If the items are exactly the same as the existing ones, we return
|
||||
// early to reuse the existing result.
|
||||
return existing;
|
||||
}
|
||||
|
||||
const reduced = reduce(mapped, ids, context.args);
|
||||
|
||||
if (existing && resultCompare(existing.reduced, reduced)) {
|
||||
return existing;
|
||||
}
|
||||
|
||||
return { mapped, reduced };
|
||||
});
|
||||
}
|
||||
|
||||
function makeResourceMapper(map) {
|
||||
return map.needsArgs
|
||||
? makeResourceArgsMapper(map)
|
||||
: makeResourceNoArgsMapper(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resources loaded when things care about arguments need to be given a
|
||||
* special ResourceIdentity object that correlates with both the resource
|
||||
* _and_ the arguments being passed to the query. That means they need extra
|
||||
* logic when loading those resources.
|
||||
*/
|
||||
function makeResourceArgsMapper(map) {
|
||||
const mapper = (value, identity, context) =>
|
||||
map(value, getIdentity(context.identMap, identity), context.args);
|
||||
return (state, id, context) => getCachedResource(state, id, context, mapper);
|
||||
}
|
||||
|
||||
function makeResourceNoArgsMapper(map) {
|
||||
const mapper = (value, identity, context) => map(value, identity);
|
||||
return (state, id, context) => getCachedResource(state, id, context, mapper);
|
||||
}
|
||||
|
||||
function getCachedResource(state, id, context, map) {
|
||||
const validatedState = getValidatedResource(state, id);
|
||||
if (!validatedState) {
|
||||
throw new Error(`Resource ${id} does not exist`);
|
||||
}
|
||||
|
||||
return map(validatedState.values[id], validatedState.identity[id], context);
|
||||
}
|
||||
|
||||
function getIdentity(identMap, identity) {
|
||||
let ident = identMap.get(identity);
|
||||
if (!ident) {
|
||||
ident = makeIdentity();
|
||||
identMap.set(identity, ident);
|
||||
}
|
||||
|
||||
return ident;
|
||||
}
|
|
@ -1,139 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
export function createInitial() {
|
||||
return {
|
||||
identity: {},
|
||||
values: {},
|
||||
};
|
||||
}
|
||||
|
||||
export function insertResources(state, resources) {
|
||||
if (resources.length === 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
state = {
|
||||
identity: { ...state.identity },
|
||||
values: { ...state.values },
|
||||
};
|
||||
|
||||
for (const resource of resources) {
|
||||
const { id } = resource;
|
||||
if (state.identity[id]) {
|
||||
throw new Error(
|
||||
`Resource "${id}" already exists, cannot insert ${JSON.stringify(
|
||||
resource
|
||||
)}`
|
||||
);
|
||||
}
|
||||
if (state.values[id]) {
|
||||
throw new Error(
|
||||
`Resource state corrupt: ${id} has value but no identity`
|
||||
);
|
||||
}
|
||||
|
||||
state.identity[resource.id] = makeIdentity();
|
||||
state.values[resource.id] = resource;
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
export function removeResources(state, resources) {
|
||||
if (resources.length === 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
state = {
|
||||
identity: { ...state.identity },
|
||||
values: { ...state.values },
|
||||
};
|
||||
|
||||
for (let id of resources) {
|
||||
if (typeof id !== "string") {
|
||||
id = id.id;
|
||||
}
|
||||
|
||||
if (!state.identity[id]) {
|
||||
throw new Error(`Resource "${id}" does not exists, cannot remove`);
|
||||
}
|
||||
if (!state.values[id]) {
|
||||
throw new Error(
|
||||
`Resource state corrupt: ${id} has identity but no value`
|
||||
);
|
||||
}
|
||||
|
||||
delete state.identity[id];
|
||||
delete state.values[id];
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
export function updateResources(state, resources) {
|
||||
if (resources.length === 0) {
|
||||
return state;
|
||||
}
|
||||
|
||||
let didCopyValues = false;
|
||||
|
||||
for (const subset of resources) {
|
||||
const { id } = subset;
|
||||
|
||||
if (!state.identity[id]) {
|
||||
throw new Error(`Resource "${id}" does not exists, cannot update`);
|
||||
}
|
||||
if (!state.values[id]) {
|
||||
throw new Error(
|
||||
`Resource state corrupt: ${id} has identity but no value`
|
||||
);
|
||||
}
|
||||
|
||||
const existing = state.values[id];
|
||||
const updated = {};
|
||||
|
||||
for (const field of Object.keys(subset)) {
|
||||
if (field === "id") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (subset[field] !== existing[field]) {
|
||||
updated[field] = subset[field];
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updated).length > 0) {
|
||||
if (!didCopyValues) {
|
||||
didCopyValues = true;
|
||||
state = {
|
||||
identity: state.identity,
|
||||
values: { ...state.values },
|
||||
};
|
||||
}
|
||||
|
||||
state.values[id] = { ...existing, ...updated };
|
||||
}
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export function makeIdentity() {
|
||||
return {};
|
||||
}
|
||||
|
||||
export function getValidatedResource(state, id) {
|
||||
const value = state.values[id];
|
||||
const identity = state.identity[id];
|
||||
if ((value && !identity) || (!value && identity)) {
|
||||
throw new Error(
|
||||
`Resource state corrupt: ${id} has mismatched value and identity`
|
||||
);
|
||||
}
|
||||
|
||||
return value ? state : null;
|
||||
}
|
||||
|
||||
export function getResourceValues(state) {
|
||||
return state.values;
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
export {
|
||||
createInitial,
|
||||
insertResources,
|
||||
removeResources,
|
||||
updateResources,
|
||||
getResourceValues,
|
||||
} from "./core";
|
||||
|
||||
export {
|
||||
hasResource,
|
||||
getResourceIds,
|
||||
getResource,
|
||||
getMappedResource,
|
||||
} from "./selector";
|
||||
|
||||
export { makeResourceQuery, makeMapWithArgs } from "./base-query";
|
||||
|
||||
export {
|
||||
filterAllIds,
|
||||
makeWeakQuery,
|
||||
makeStrictQuery,
|
||||
makeIdQuery,
|
||||
makeLoadQuery,
|
||||
makeFilterQuery,
|
||||
makeReduceAllQuery,
|
||||
} from "./query";
|
||||
|
||||
export {
|
||||
queryCacheWeak,
|
||||
queryCacheShallow,
|
||||
queryCacheStrict,
|
||||
} from "./query-cache";
|
||||
|
||||
export { shallowEqual } from "./compare";
|
||||
|
||||
export { memoizeResourceShallow } from "./memoize";
|
|
@ -1,45 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import { shallowEqual } from "./compare";
|
||||
|
||||
/**
|
||||
* Wraps a 'mapper' function to create a shallow-equality memoized version
|
||||
* of the mapped result. The returned function will return the same value
|
||||
* even if the input object is different, as long as the identity is the same
|
||||
* and the mapped result is shallow-equal to the most recent mapped value.
|
||||
*/
|
||||
export function memoizeResourceShallow(map) {
|
||||
const cache = new WeakMap();
|
||||
|
||||
const fn = (input, identity, args) => {
|
||||
let existingEntry = cache.get(identity);
|
||||
|
||||
if (!existingEntry || existingEntry.input !== input) {
|
||||
const mapper = map;
|
||||
const output = mapper(input, identity, args);
|
||||
|
||||
if (existingEntry) {
|
||||
// If the new output is shallow-equal to the old output, we reuse
|
||||
// the previous object instead to preserve object equality.
|
||||
const newOutput = shallowEqual(output, existingEntry.output)
|
||||
? existingEntry.output
|
||||
: output;
|
||||
|
||||
existingEntry.output = newOutput;
|
||||
existingEntry.input = input;
|
||||
} else {
|
||||
existingEntry = {
|
||||
input,
|
||||
output,
|
||||
};
|
||||
cache.set(identity, existingEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return existingEntry.output;
|
||||
};
|
||||
fn.needsArgs = map.needsArgs;
|
||||
return fn;
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
DIRS += []
|
||||
|
||||
CompiledModules(
|
||||
"base-query.js",
|
||||
"compare.js",
|
||||
"core.js",
|
||||
"index.js",
|
||||
"memoize.js",
|
||||
"query-cache.js",
|
||||
"query.js",
|
||||
"selector.js",
|
||||
)
|
|
@ -1,93 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import { strictEqual, shallowEqual } from "./compare";
|
||||
|
||||
/**
|
||||
* A query 'cache' function that uses the identity of the arguments object to
|
||||
* cache data for the query itself.
|
||||
*/
|
||||
export function queryCacheWeak(handler) {
|
||||
const cache = new WeakMap();
|
||||
return makeCacheFunction({
|
||||
handler,
|
||||
// The WeakMap will only return entries for the exact object,
|
||||
// so there is no need to compare at all.
|
||||
compareArgs: () => true,
|
||||
getEntry: args => cache.get(args) || null,
|
||||
setEntry: (args, entry) => {
|
||||
cache.set(args, entry);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A query 'cache' function that uses shallow comparison to cache the most
|
||||
* recent calculated result based on the value of the argument.
|
||||
*/
|
||||
export function queryCacheShallow(handler) {
|
||||
let latestEntry = null;
|
||||
return makeCacheFunction({
|
||||
handler,
|
||||
compareArgs: shallowEqual,
|
||||
getEntry: () => latestEntry,
|
||||
setEntry: (args, entry) => {
|
||||
latestEntry = entry;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A query 'cache' function that uses strict comparison to cache the most
|
||||
* recent calculated result based on the value of the argument.
|
||||
*/
|
||||
export function queryCacheStrict(handler) {
|
||||
let latestEntry = null;
|
||||
return makeCacheFunction({
|
||||
handler,
|
||||
compareArgs: strictEqual,
|
||||
getEntry: () => latestEntry,
|
||||
setEntry: (args, entry) => {
|
||||
latestEntry = entry;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function makeCacheFunction(info) {
|
||||
const { handler, compareArgs, getEntry, setEntry } = info;
|
||||
|
||||
return (state, args) => {
|
||||
let entry = getEntry(args);
|
||||
|
||||
const sameArgs = !!entry && compareArgs(entry.context.args, args);
|
||||
const sameState = !!entry && entry.state === state;
|
||||
|
||||
if (!entry || !sameArgs || !sameState) {
|
||||
const context =
|
||||
!entry || !sameArgs
|
||||
? {
|
||||
args,
|
||||
identMap: new WeakMap(),
|
||||
}
|
||||
: entry.context;
|
||||
|
||||
const result = handler(state, context, entry ? entry.result : null);
|
||||
|
||||
if (entry) {
|
||||
entry.context = context;
|
||||
entry.state = state;
|
||||
entry.result = result;
|
||||
} else {
|
||||
entry = {
|
||||
context,
|
||||
state,
|
||||
result,
|
||||
};
|
||||
setEntry(args, entry);
|
||||
}
|
||||
}
|
||||
|
||||
return entry.result.reduced;
|
||||
};
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import { makeResourceQuery } from "./base-query";
|
||||
|
||||
import { queryCacheWeak, queryCacheStrict } from "./query-cache";
|
||||
|
||||
import { memoizeResourceShallow } from "./memoize";
|
||||
import { shallowEqual } from "./compare";
|
||||
|
||||
export function filterAllIds(values) {
|
||||
return Object.keys(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query function to take a list of IDs and map each Reduceding
|
||||
* resource object into a mapped form.
|
||||
*/
|
||||
export function makeWeakQuery({ filter, map, reduce }) {
|
||||
return makeResourceQuery({
|
||||
cache: queryCacheWeak,
|
||||
filter,
|
||||
map: memoizeResourceShallow(map),
|
||||
reduce,
|
||||
resultCompare: shallowEqual,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query function to take a list of IDs and map each Reduceding
|
||||
* resource object into a mapped form.
|
||||
*/
|
||||
export function makeStrictQuery({ filter, map, reduce }) {
|
||||
return makeResourceQuery({
|
||||
cache: queryCacheStrict,
|
||||
filter,
|
||||
map: memoizeResourceShallow(map),
|
||||
reduce,
|
||||
resultCompare: shallowEqual,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query function to take a list of IDs and map each Reduceding
|
||||
* resource object into a mapped form.
|
||||
*/
|
||||
export function makeIdQuery(map) {
|
||||
return makeWeakQuery({
|
||||
filter: (state, ids) => ids,
|
||||
map: (r, identity) => map(r, identity),
|
||||
reduce: items => items.slice(),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query function to take a list of IDs and map each Reduceding
|
||||
* resource object into a mapped form.
|
||||
*/
|
||||
export function makeLoadQuery(map) {
|
||||
return makeWeakQuery({
|
||||
filter: (state, ids) => ids,
|
||||
map: (r, identity) => map(r, identity),
|
||||
reduce: reduceMappedArrayToObject,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query function that accepts an argument and can filter the
|
||||
* resource items to a subset before mapping each reduced resource.
|
||||
*/
|
||||
export function makeFilterQuery(filter, map) {
|
||||
return makeWeakQuery({
|
||||
filter: (values, args) => {
|
||||
const ids = [];
|
||||
for (const id of Object.keys(values)) {
|
||||
if (filter(values[id], args)) {
|
||||
ids.push(id);
|
||||
}
|
||||
}
|
||||
return ids;
|
||||
},
|
||||
map,
|
||||
reduce: reduceMappedArrayToObject,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a query function that accepts an argument and can filter the
|
||||
* resource items to a subset before mapping each resulting resource.
|
||||
*/
|
||||
export function makeReduceAllQuery(map, reduce) {
|
||||
return makeStrictQuery({
|
||||
filter: filterAllIds,
|
||||
map,
|
||||
reduce,
|
||||
});
|
||||
}
|
||||
|
||||
function reduceMappedArrayToObject(items, ids, args) {
|
||||
return items.reduce((acc, item, i) => {
|
||||
acc[ids[i]] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import { getValidatedResource, getResourceValues } from "./core";
|
||||
|
||||
export function hasResource(state, id) {
|
||||
return !!getValidatedResource(state, id);
|
||||
}
|
||||
|
||||
export function getResourceIds(state) {
|
||||
return Object.keys(getResourceValues(state));
|
||||
}
|
||||
|
||||
export function getResource(state, id) {
|
||||
const validatedState = getValidatedResource(state, id);
|
||||
if (!validatedState) {
|
||||
throw new Error(`Resource ${id} does not exist`);
|
||||
}
|
||||
return validatedState.values[id];
|
||||
}
|
||||
|
||||
export function getMappedResource(state, id, map) {
|
||||
const validatedState = getValidatedResource(state, id);
|
||||
if (!validatedState) {
|
||||
throw new Error(`Resource ${id} does not exist`);
|
||||
}
|
||||
|
||||
return map(validatedState.values[id], validatedState.identity[id]);
|
||||
}
|
|
@ -1,252 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import {
|
||||
createInitial,
|
||||
insertResources,
|
||||
removeResources,
|
||||
updateResources,
|
||||
hasResource,
|
||||
getResourceIds,
|
||||
getResource,
|
||||
getMappedResource,
|
||||
} from "..";
|
||||
|
||||
const makeResource = id => ({
|
||||
id,
|
||||
name: `name-${id}`,
|
||||
data: 42,
|
||||
obj: {},
|
||||
});
|
||||
|
||||
const mapName = resource => resource.name;
|
||||
const mapWithIdent = (resource, identity) => ({
|
||||
resource,
|
||||
identity,
|
||||
obj: {},
|
||||
});
|
||||
|
||||
const clone = v => JSON.parse(JSON.stringify(v));
|
||||
|
||||
describe("resource CRUD operations", () => {
|
||||
let r1, r2, r3;
|
||||
let originalInitial;
|
||||
let initialState;
|
||||
beforeEach(() => {
|
||||
r1 = makeResource("id-1");
|
||||
r2 = makeResource("id-2");
|
||||
r3 = makeResource("id-3");
|
||||
|
||||
initialState = createInitial();
|
||||
originalInitial = clone(initialState);
|
||||
});
|
||||
|
||||
describe("insert", () => {
|
||||
it("should work", () => {
|
||||
const state = insertResources(initialState, [r1, r2, r3]);
|
||||
|
||||
expect(initialState).toEqual(originalInitial);
|
||||
expect(getResource(state, r1.id)).toBe(r1);
|
||||
expect(getResource(state, r2.id)).toBe(r2);
|
||||
expect(getResource(state, r3.id)).toBe(r3);
|
||||
});
|
||||
|
||||
it("should throw on duplicate", () => {
|
||||
const state = insertResources(initialState, [r1]);
|
||||
expect(() => {
|
||||
insertResources(state, [r1]);
|
||||
}).toThrow(/already exists/);
|
||||
|
||||
expect(() => {
|
||||
insertResources(state, [r2, r2]);
|
||||
}).toThrow(/already exists/);
|
||||
});
|
||||
|
||||
it("should be a no-op when given no resources", () => {
|
||||
const state = insertResources(initialState, []);
|
||||
|
||||
expect(state).toBe(initialState);
|
||||
});
|
||||
});
|
||||
|
||||
describe("read", () => {
|
||||
beforeEach(() => {
|
||||
initialState = insertResources(initialState, [r1, r2, r3]);
|
||||
});
|
||||
|
||||
it("should allow reading all IDs", () => {
|
||||
expect(getResourceIds(initialState)).toEqual([r1.id, r2.id, r3.id]);
|
||||
});
|
||||
|
||||
it("should allow checking for existing of an ID", () => {
|
||||
expect(hasResource(initialState, r1.id)).toBe(true);
|
||||
expect(hasResource(initialState, r2.id)).toBe(true);
|
||||
expect(hasResource(initialState, r3.id)).toBe(true);
|
||||
expect(hasResource(initialState, "unknownId")).toBe(false);
|
||||
});
|
||||
|
||||
it("should allow reading an item", () => {
|
||||
expect(getResource(initialState, r1.id)).toBe(r1);
|
||||
expect(getResource(initialState, r2.id)).toBe(r2);
|
||||
expect(getResource(initialState, r3.id)).toBe(r3);
|
||||
|
||||
expect(() => {
|
||||
getResource(initialState, "unknownId");
|
||||
}).toThrow(/does not exist/);
|
||||
});
|
||||
|
||||
it("should allow reading and mapping an item", () => {
|
||||
expect(getMappedResource(initialState, r1.id, mapName)).toBe(r1.name);
|
||||
expect(getMappedResource(initialState, r2.id, mapName)).toBe(r2.name);
|
||||
expect(getMappedResource(initialState, r3.id, mapName)).toBe(r3.name);
|
||||
|
||||
expect(() => {
|
||||
getMappedResource(initialState, "unknownId", mapName);
|
||||
}).toThrow(/does not exist/);
|
||||
});
|
||||
|
||||
it("should allow reading and mapping an item with identity", () => {
|
||||
const r1Ident = getMappedResource(initialState, r1.id, mapWithIdent);
|
||||
const r2Ident = getMappedResource(initialState, r2.id, mapWithIdent);
|
||||
|
||||
const state = updateResources(initialState, [{ ...r1, obj: {} }]);
|
||||
|
||||
const r1NewIdent = getMappedResource(state, r1.id, mapWithIdent);
|
||||
const r2NewIdent = getMappedResource(state, r2.id, mapWithIdent);
|
||||
|
||||
// The update changed the resource object, but not the identity.
|
||||
expect(r1NewIdent.resource).not.toBe(r1Ident.resource);
|
||||
expect(r1NewIdent.resource).toEqual(r1Ident.resource);
|
||||
expect(r1NewIdent.identity).toBe(r1Ident.identity);
|
||||
|
||||
// The update did not change the r2 resource.
|
||||
expect(r2NewIdent.resource).toBe(r2Ident.resource);
|
||||
expect(r2NewIdent.identity).toBe(r2Ident.identity);
|
||||
});
|
||||
});
|
||||
|
||||
describe("update", () => {
|
||||
beforeEach(() => {
|
||||
initialState = insertResources(initialState, [r1, r2, r3]);
|
||||
originalInitial = clone(initialState);
|
||||
});
|
||||
|
||||
it("should work", () => {
|
||||
const r1Ident = getMappedResource(initialState, r1.id, mapWithIdent);
|
||||
const r2Ident = getMappedResource(initialState, r2.id, mapWithIdent);
|
||||
const r3Ident = getMappedResource(initialState, r3.id, mapWithIdent);
|
||||
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r1.id,
|
||||
data: 21,
|
||||
},
|
||||
{
|
||||
id: r2.id,
|
||||
name: "newName",
|
||||
},
|
||||
]);
|
||||
|
||||
expect(initialState).toEqual(originalInitial);
|
||||
expect(getResource(state, r1.id)).toEqual({ ...r1, data: 21 });
|
||||
expect(getResource(state, r2.id)).toEqual({ ...r2, name: "newName" });
|
||||
expect(getResource(state, r3.id)).toBe(r3);
|
||||
|
||||
const r1NewIdent = getMappedResource(state, r1.id, mapWithIdent);
|
||||
const r2NewIdent = getMappedResource(state, r2.id, mapWithIdent);
|
||||
const r3NewIdent = getMappedResource(state, r3.id, mapWithIdent);
|
||||
|
||||
// The update changed the resource object, but not the identity.
|
||||
expect(r1NewIdent.resource).not.toBe(r1Ident.resource);
|
||||
expect(r1NewIdent.resource).toEqual({
|
||||
...r1Ident.resource,
|
||||
data: 21,
|
||||
});
|
||||
expect(r1NewIdent.identity).toBe(r1Ident.identity);
|
||||
|
||||
// The update changed the resource object, but not the identity.
|
||||
expect(r2NewIdent.resource).toEqual({
|
||||
...r2Ident.resource,
|
||||
name: "newName",
|
||||
});
|
||||
expect(r2NewIdent.identity).toBe(r2Ident.identity);
|
||||
|
||||
// The update did not change the r3 resource.
|
||||
expect(r3NewIdent.resource).toBe(r3Ident.resource);
|
||||
expect(r3NewIdent.identity).toBe(r3Ident.identity);
|
||||
});
|
||||
|
||||
it("should throw if not found", () => {
|
||||
expect(() => {
|
||||
updateResources(initialState, [
|
||||
{
|
||||
...r1,
|
||||
id: "unknownId",
|
||||
},
|
||||
]);
|
||||
}).toThrow(/does not exists/);
|
||||
});
|
||||
|
||||
it("should be a no-op when new fields are strict-equal", () => {
|
||||
const state = updateResources(initialState, [r1]);
|
||||
expect(state).toBe(initialState);
|
||||
});
|
||||
|
||||
it("should be a no-op when given no resources", () => {
|
||||
const state = updateResources(initialState, []);
|
||||
expect(state).toBe(initialState);
|
||||
});
|
||||
});
|
||||
|
||||
describe("delete", () => {
|
||||
beforeEach(() => {
|
||||
initialState = insertResources(initialState, [r1, r2, r3]);
|
||||
originalInitial = clone(initialState);
|
||||
});
|
||||
|
||||
it("should work with objects", () => {
|
||||
const state = removeResources(initialState, [r1]);
|
||||
|
||||
expect(initialState).toEqual(originalInitial);
|
||||
expect(hasResource(state, r1.id)).toBe(false);
|
||||
expect(hasResource(state, r2.id)).toBe(true);
|
||||
expect(hasResource(state, r3.id)).toBe(true);
|
||||
});
|
||||
|
||||
it("should work with object subsets", () => {
|
||||
const state = removeResources(initialState, [{ id: r1.id }]);
|
||||
|
||||
expect(initialState).toEqual(originalInitial);
|
||||
expect(hasResource(state, r1.id)).toBe(false);
|
||||
expect(hasResource(state, r2.id)).toBe(true);
|
||||
expect(hasResource(state, r3.id)).toBe(true);
|
||||
});
|
||||
|
||||
it("should work with ids", () => {
|
||||
const state = removeResources(initialState, [r1.id]);
|
||||
|
||||
expect(initialState).toEqual(originalInitial);
|
||||
expect(hasResource(state, r1.id)).toBe(false);
|
||||
expect(hasResource(state, r2.id)).toBe(true);
|
||||
expect(hasResource(state, r3.id)).toBe(true);
|
||||
});
|
||||
|
||||
it("should throw if not found", () => {
|
||||
expect(() => {
|
||||
removeResources(initialState, [makeResource("unknownId")]);
|
||||
}).toThrow(/does not exist/);
|
||||
expect(() => {
|
||||
removeResources(initialState, [{ id: "unknownId" }]);
|
||||
}).toThrow(/does not exist/);
|
||||
expect(() => {
|
||||
removeResources(initialState, ["unknownId"]);
|
||||
}).toThrow(/does not exist/);
|
||||
});
|
||||
|
||||
it("should be a no-op when given no resources", () => {
|
||||
const state = removeResources(initialState, []);
|
||||
expect(state).toBe(initialState);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,720 +0,0 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
import {
|
||||
createInitial,
|
||||
insertResources,
|
||||
updateResources,
|
||||
makeMapWithArgs,
|
||||
makeWeakQuery,
|
||||
makeStrictQuery,
|
||||
} from "..";
|
||||
|
||||
const makeResource = id => ({
|
||||
id,
|
||||
name: `name-${id}`,
|
||||
data: 42,
|
||||
obj: {},
|
||||
});
|
||||
|
||||
// Jest's mock type just wouldn't cooperate below, so this is a custom version
|
||||
// that does what I need.
|
||||
|
||||
// We need to pass the 'needsArgs' prop through to the query fn so we use
|
||||
// this utility to do that and at the same time preserve typechecking.
|
||||
const mockFn = f => Object.assign(jest.fn(f), f);
|
||||
|
||||
const mockFilter = callback => mockFn(callback);
|
||||
const mockMapNoArgs = callback => mockFn(callback);
|
||||
const mockMapWithArgs = callback => mockFn(makeMapWithArgs(callback));
|
||||
const mockReduce = callback => mockFn(callback);
|
||||
|
||||
describe("resource query operations", () => {
|
||||
let r1, r2, r3;
|
||||
let initialState;
|
||||
let mapNoArgs, mapWithArgs, reduce;
|
||||
|
||||
beforeEach(() => {
|
||||
r1 = makeResource("id-1");
|
||||
r2 = makeResource("id-2");
|
||||
r3 = makeResource("id-3");
|
||||
|
||||
initialState = createInitial();
|
||||
|
||||
initialState = insertResources(initialState, [r1, r2, r3]);
|
||||
|
||||
mapNoArgs = mockMapNoArgs((resource, ident) => resource);
|
||||
mapWithArgs = mockMapWithArgs((resource, ident, args) => resource);
|
||||
reduce = mockReduce((mapped, ids, args) => {
|
||||
return mapped.reduce((acc, item, i) => {
|
||||
acc[ids[i]] = item;
|
||||
return acc;
|
||||
}, {});
|
||||
});
|
||||
});
|
||||
|
||||
describe("weak cache", () => {
|
||||
let filter;
|
||||
|
||||
beforeEach(() => {
|
||||
filter = mockFilter(
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
(values, args) => args
|
||||
);
|
||||
});
|
||||
|
||||
describe("no args", () => {
|
||||
let query;
|
||||
|
||||
beforeEach(() => {
|
||||
query = makeWeakQuery({ filter, map: mapNoArgs, reduce });
|
||||
});
|
||||
|
||||
it("should return same with same state and same args", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result2 = query(initialState, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return same with updated other state and same args 1", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Updating r2 does not affect cached result that only cares about r2.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r3.id,
|
||||
obj: {},
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return same with updated other state and same args 2", () => {
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
mapNoArgs.mockImplementation(resource => ({ ...resource, name: "" }));
|
||||
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: { ...r1, name: "" },
|
||||
[r2.id]: { ...r2, name: "" },
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Since the map function ignores the name value, updating it should
|
||||
// not reset the cached for this query.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r3.id,
|
||||
name: "newName",
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: { ...r1, name: "" },
|
||||
[r2.id]: { ...r2, name: "" },
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return diff with updated id state and same args", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Since the mapper returns a value with name, changing a name will
|
||||
// invalidate the cache.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r1.id,
|
||||
name: "newName",
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).not.toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: { ...r1, name: "newName" },
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(3);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("should return diff with same state and diff args", () => {
|
||||
const firstArgs = [r1.id, r2.id];
|
||||
const secondArgs = [r1.id, r2.id];
|
||||
const result1 = query(initialState, firstArgs);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result2 = query(initialState, secondArgs);
|
||||
expect(result2).not.toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
|
||||
// Same result from first query still available.
|
||||
const result3 = query(initialState, firstArgs);
|
||||
expect(result3).not.toBe(result2);
|
||||
expect(result3).toBe(result1);
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
|
||||
// Same result from second query still available.
|
||||
const result4 = query(initialState, secondArgs);
|
||||
expect(result4).toBe(result2);
|
||||
expect(result4).not.toBe(result3);
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with args", () => {
|
||||
let query;
|
||||
|
||||
beforeEach(() => {
|
||||
query = makeWeakQuery({ filter, map: mapWithArgs, reduce });
|
||||
});
|
||||
|
||||
it("should return same with same state and same args", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result2 = query(initialState, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return same with updated other state and same args 1", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Updating r2 does not affect cached result that only cares about r2.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r3.id,
|
||||
obj: {},
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return same with updated other state and same args 2", () => {
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
mapWithArgs.mockImplementation(resource => ({ ...resource, name: "" }));
|
||||
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: { ...r1, name: "" },
|
||||
[r2.id]: { ...r2, name: "" },
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Since the map function ignores the name value, updating it should
|
||||
// not reset the cached for this query.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r3.id,
|
||||
name: "newName",
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: { ...r1, name: "" },
|
||||
[r2.id]: { ...r2, name: "" },
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return diff with updated id state and same args", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Since the mapper returns a value with name, changing a name will
|
||||
// invalidate the cache.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r1.id,
|
||||
name: "newName",
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).not.toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: { ...r1, name: "newName" },
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(3);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("should return diff with same state and diff args", () => {
|
||||
const firstArgs = [r1.id, r2.id];
|
||||
const secondArgs = [r1.id, r2.id];
|
||||
|
||||
const result1 = query(initialState, firstArgs);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result2 = query(initialState, secondArgs);
|
||||
expect(result2).not.toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(4);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
|
||||
// Same result from first query still available.
|
||||
const result3 = query(initialState, firstArgs);
|
||||
expect(result3).not.toBe(result2);
|
||||
expect(result3).toBe(result1);
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(4);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
|
||||
// Same result from second query still available.
|
||||
const result4 = query(initialState, secondArgs);
|
||||
expect(result4).toBe(result2);
|
||||
expect(result4).not.toBe(result3);
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(4);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("strict cache", () => {
|
||||
let filter;
|
||||
|
||||
beforeEach(() => {
|
||||
filter = mockFilter(
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
(values, ids) => ids
|
||||
);
|
||||
});
|
||||
|
||||
describe("no args", () => {
|
||||
let query;
|
||||
|
||||
beforeEach(() => {
|
||||
query = makeStrictQuery({ filter, map: mapNoArgs, reduce });
|
||||
});
|
||||
|
||||
it("should return same with same state and same args", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result2 = query(initialState, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return same with updated other state and same args 1", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Updating r2 does not affect cached result that only cares about r2.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r3.id,
|
||||
obj: {},
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return same with updated other state and same args 2", () => {
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
mapNoArgs.mockImplementation(resource => ({ ...resource, name: "" }));
|
||||
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: { ...r1, name: "" },
|
||||
[r2.id]: { ...r2, name: "" },
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Since the map function ignores the name value, updating it should
|
||||
// not reset the cached for this query.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r3.id,
|
||||
name: "newName",
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: { ...r1, name: "" },
|
||||
[r2.id]: { ...r2, name: "" },
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return diff with updated id state and same args", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Since the mapper returns a value with name, changing a name will
|
||||
// invalidate the cache.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r1.id,
|
||||
name: "newName",
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).not.toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: { ...r1, name: "newName" },
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(3);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("should return diff with same state and diff args", () => {
|
||||
const firstArgs = [r1.id, r2.id];
|
||||
const secondArgs = [r1.id, r2.id];
|
||||
const result1 = query(initialState, firstArgs);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result2 = query(initialState, secondArgs);
|
||||
expect(result2).toBe(result1);
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result3 = query(initialState, firstArgs);
|
||||
expect(result3).toBe(result2);
|
||||
expect(filter.mock.calls).toHaveLength(3);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result4 = query(initialState, secondArgs);
|
||||
expect(result4).toBe(result3);
|
||||
expect(filter.mock.calls).toHaveLength(4);
|
||||
expect(mapNoArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("with args", () => {
|
||||
let query;
|
||||
|
||||
beforeEach(() => {
|
||||
query = makeStrictQuery({ filter, map: mapWithArgs, reduce });
|
||||
});
|
||||
|
||||
it("should return same with same state and same args", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result2 = query(initialState, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return same with updated other state and same args 1", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Updating r2 does not affect cached result that only cares about r2.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r3.id,
|
||||
obj: {},
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return same with updated other state and same args 2", () => {
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
mapWithArgs.mockImplementation(resource => ({ ...resource, name: "" }));
|
||||
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: { ...r1, name: "" },
|
||||
[r2.id]: { ...r2, name: "" },
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Since the map function ignores the name value, updating it should
|
||||
// not reset the cached for this query.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r3.id,
|
||||
name: "newName",
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: { ...r1, name: "" },
|
||||
[r2.id]: { ...r2, name: "" },
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("should return diff with updated id state and same args", () => {
|
||||
const args = [r1.id, r2.id];
|
||||
const result1 = query(initialState, args);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
// Since the mapper returns a value with name, changing a name will
|
||||
// invalidate the cache.
|
||||
const state = updateResources(initialState, [
|
||||
{
|
||||
id: r1.id,
|
||||
name: "newName",
|
||||
},
|
||||
]);
|
||||
|
||||
const result2 = query(state, args);
|
||||
expect(result2).not.toBe(result1);
|
||||
expect(result2).toEqual({
|
||||
[r1.id]: { ...r1, name: "newName" },
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(3);
|
||||
expect(reduce.mock.calls).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("should return diff with same state and diff args", () => {
|
||||
const firstArgs = [r1.id, r2.id];
|
||||
const secondArgs = [r1.id, r2.id];
|
||||
|
||||
const result1 = query(initialState, firstArgs);
|
||||
expect(result1).toEqual({
|
||||
[r1.id]: r1,
|
||||
[r2.id]: r2,
|
||||
});
|
||||
expect(filter.mock.calls).toHaveLength(1);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(2);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result2 = query(initialState, secondArgs);
|
||||
expect(result2).toBe(result1);
|
||||
expect(filter.mock.calls).toHaveLength(2);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(4);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result3 = query(initialState, firstArgs);
|
||||
expect(result3).toBe(result2);
|
||||
expect(filter.mock.calls).toHaveLength(3);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(6);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
|
||||
const result4 = query(initialState, secondArgs);
|
||||
expect(result4).toBe(result3);
|
||||
expect(filter.mock.calls).toHaveLength(4);
|
||||
expect(mapWithArgs.mock.calls).toHaveLength(8);
|
||||
expect(reduce.mock.calls).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,10 +2,6 @@
|
|||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
|
||||
|
||||
export function strictEqual(value, other) {
|
||||
return value === other;
|
||||
}
|
||||
|
||||
export function shallowEqual(value, other) {
|
||||
return (
|
||||
value === other ||
|
Загрузка…
Ссылка в новой задаче