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:
Alexandre Poirot 2022-04-06 07:17:41 +00:00
Родитель 02c69d75ac
Коммит 2607e2017e
14 изменённых файлов: 3 добавлений и 1531 удалений

Просмотреть файл

@ -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 ||