Tests for searchFolderTree part I.
parent
eeb199b816
commit
32bbe8ad96
|
@ -4,27 +4,28 @@ import { collapseAll } from "../actions";
|
|||
import { sample } from "lodash";
|
||||
import { cloneAndClimb, climb } from "../climb";
|
||||
|
||||
// Folder structure used in tests:
|
||||
// ├─ One
|
||||
// ├─ Two
|
||||
// │ └─ Three
|
||||
// ├─ Four
|
||||
// │ └─ Five
|
||||
// ├─ Six
|
||||
// │ └─ Seven
|
||||
// │ ├─ Eight
|
||||
// │ └─ Nine
|
||||
// ├─ Ten
|
||||
// │ ├─ Eleven
|
||||
// │ └─ Twelve
|
||||
// │ └─ Thirteen
|
||||
// └─ Fourteen
|
||||
// ├─ Fifteen
|
||||
// └─ Sixteen
|
||||
// ├─ Seventeen
|
||||
// └─ Eighteen
|
||||
|
||||
const FOLDERS: FolderNode[] = [
|
||||
/** A set of fake Folder resources used exclusively for testing purposes.
|
||||
```
|
||||
├─ One
|
||||
├─ Two
|
||||
│ └─ Three
|
||||
├─ Four
|
||||
│ └─ Five
|
||||
├─ Six
|
||||
│ └─ Seven
|
||||
│ ├─ Eight
|
||||
│ └─ Nine
|
||||
├─ Ten
|
||||
│ ├─ Eleven
|
||||
│ └─ Twelve
|
||||
│ └─ Thirteen
|
||||
└─ Fourteen
|
||||
├─ Fifteen
|
||||
└─ Sixteen
|
||||
├─ Seventeen
|
||||
└─ Eighteen
|
||||
``` */
|
||||
const TEST_FOLDERS: FolderNode[] = [
|
||||
{ id: 1, parent_id: undefined, color: "blue", name: "One" },
|
||||
{ id: 2, parent_id: undefined, color: "blue", name: "Two" },
|
||||
{ id: 3, parent_id: 2, color: "blue", name: "Three" },
|
||||
|
@ -45,8 +46,30 @@ const FOLDERS: FolderNode[] = [
|
|||
{ id: 18, parent_id: 16, color: "blue", name: "Eighteen" }
|
||||
];
|
||||
|
||||
const GRAPH = ingest({
|
||||
folders: FOLDERS,
|
||||
/**
|
||||
```
|
||||
├─ One
|
||||
├─ Two
|
||||
│ └─ Three
|
||||
├─ Four
|
||||
│ └─ Five
|
||||
├─ Six
|
||||
│ └─ Seven
|
||||
│ ├─ Eight
|
||||
│ └─ Nine
|
||||
├─ Ten
|
||||
│ ├─ Eleven
|
||||
│ └─ Twelve
|
||||
│ └─ Thirteen
|
||||
└─ Fourteen
|
||||
├─ Fifteen
|
||||
└─ Sixteen
|
||||
├─ Seventeen
|
||||
└─ Eighteen
|
||||
```
|
||||
*/
|
||||
export const TEST_GRAPH = ingest({
|
||||
folders: TEST_FOLDERS,
|
||||
localMetaAttributes: {}
|
||||
});
|
||||
|
||||
|
@ -55,7 +78,7 @@ describe("deletion of folders", () => {
|
|||
});
|
||||
|
||||
describe("expand/collapse all", () => {
|
||||
const halfOpen = cloneAndClimb(GRAPH, (node) => {
|
||||
const halfOpen = cloneAndClimb(TEST_GRAPH, (node) => {
|
||||
node.open = !sample([true, false]);
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
import { TEST_GRAPH } from "./actions_test";
|
||||
import { searchFolderTree } from "../search_folder_tree";
|
||||
|
||||
describe("searchFolderTree", () => {
|
||||
it("Reutrns an empty result set when no match is found.", () => {
|
||||
const before = JSON.stringify(TEST_GRAPH);
|
||||
const results = searchFolderTree({
|
||||
references: {},
|
||||
input: "foo",
|
||||
root: TEST_GRAPH
|
||||
});
|
||||
const after = JSON.stringify(TEST_GRAPH);
|
||||
expect(results).toBeTruthy();
|
||||
expect(results.length).toEqual(0);
|
||||
expect(before).toEqual(after); // Prevent mutation of original data.
|
||||
});
|
||||
});
|
|
@ -4,13 +4,8 @@ import {
|
|||
FolderNodeTerminal,
|
||||
RootFolderNode,
|
||||
FolderMeta,
|
||||
FolderUnion,
|
||||
} from "./constants";
|
||||
import { sortBy } from "lodash";
|
||||
import {
|
||||
TaggedResource,
|
||||
TaggedSequence
|
||||
} from "farmbot/dist/resources/tagged_resource";
|
||||
|
||||
type FoldersIndexedByParentId = Record<number, FolderNode[]>;
|
||||
|
||||
|
@ -82,109 +77,3 @@ export const ingest: IngestFn = ({ folders, localMetaAttributes }) => {
|
|||
|
||||
return output;
|
||||
};
|
||||
interface FolderSearchProps {
|
||||
references: Record<string, TaggedResource | undefined>;
|
||||
input: string;
|
||||
root: RootFolderNode;
|
||||
}
|
||||
|
||||
const isSearchMatchSeq =
|
||||
(searchTerm: string, s?: TaggedResource): s is TaggedSequence => {
|
||||
if (s && s.kind == "Sequence") {
|
||||
const name = s.body.name.toLowerCase();
|
||||
return name.includes(searchTerm);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const isSearchMatchFolder = (searchTerm: string, f: FolderUnion) => {
|
||||
if (f.name.toLowerCase().includes(searchTerm)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/** Given an input search term, returns folder IDs (number) and Sequence UUIDs
|
||||
* that match */
|
||||
export const searchFoldersAndSequencesForTerm = (props: FolderSearchProps) => {
|
||||
// A sequence is included if:
|
||||
// * CASE 1: The name is a search match
|
||||
// * CASE 2: The containing folder is a search match.
|
||||
|
||||
// A folder is included if:
|
||||
// * CASE 3: The name is a search match
|
||||
// * CASE 4: It contains a sequence that is a match.
|
||||
// * CASE 5: It has a child that has a search match.
|
||||
|
||||
const searchTerm = props.input.toLowerCase();
|
||||
const sequenceSet = new Set<string>();
|
||||
const folderSet = new Set<FolderUnion>();
|
||||
|
||||
props.root.folders.map(level1 => {
|
||||
level1.content.map(level1Sequence => { // ========= Level 1
|
||||
if (isSearchMatchSeq(searchTerm, props.references[level1Sequence])) {
|
||||
// CASE 1:
|
||||
sequenceSet.add(level1Sequence);
|
||||
// CASE 4:
|
||||
folderSet.add(level1);
|
||||
}
|
||||
});
|
||||
|
||||
if (isSearchMatchFolder(searchTerm, level1)) {
|
||||
// CASE 2
|
||||
level1.content.map(uuid => sequenceSet.add(uuid));
|
||||
// CASE 3
|
||||
folderSet.add(level1);
|
||||
}
|
||||
|
||||
level1.children.map(level2 => { // ================ LEVEL 2
|
||||
if (isSearchMatchFolder(searchTerm, level2)) {
|
||||
// CASE 2
|
||||
level2.content.map(uuid => sequenceSet.add(uuid));
|
||||
// CASE 3
|
||||
folderSet.add(level2);
|
||||
// CASE 5
|
||||
folderSet.add(level1);
|
||||
}
|
||||
|
||||
level2.content.map(level2Sequence => {
|
||||
if (isSearchMatchSeq(searchTerm, props.references[level2Sequence])) {
|
||||
// CASE 1:
|
||||
sequenceSet.add(level2Sequence);
|
||||
// CASE 4:
|
||||
folderSet.add(level2);
|
||||
// CASE 5
|
||||
folderSet.add(level1);
|
||||
}
|
||||
});
|
||||
level2.children.map(level3 => { // ============== LEVEL 3
|
||||
if (isSearchMatchFolder(searchTerm, level3)) {
|
||||
// CASE 2
|
||||
level3.content.map(uuid => sequenceSet.add(uuid));
|
||||
// CASE 3
|
||||
folderSet.add(level3);
|
||||
// CASE 5
|
||||
folderSet.add(level2);
|
||||
// CASE 5
|
||||
folderSet.add(level1);
|
||||
}
|
||||
level3.content.map(level3Sequence => {
|
||||
if (isSearchMatchSeq(searchTerm, props.references[level3Sequence])) {
|
||||
// CASE 1:
|
||||
sequenceSet.add(level3Sequence);
|
||||
// CASE 3
|
||||
folderSet.add(level3);
|
||||
// CASE 5
|
||||
folderSet.add(level2);
|
||||
// CASE 5
|
||||
folderSet.add(level1);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(folderSet);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
import { TaggedResource, TaggedSequence } from "farmbot";
|
||||
|
||||
import { RootFolderNode, FolderUnion } from "./constants";
|
||||
|
||||
interface FolderSearchProps {
|
||||
references: Record<string, TaggedResource | undefined>;
|
||||
input: string;
|
||||
root: RootFolderNode;
|
||||
}
|
||||
|
||||
const isSearchMatchSeq =
|
||||
(searchTerm: string, s?: TaggedResource): s is TaggedSequence => {
|
||||
if (s && s.kind == "Sequence") {
|
||||
const name = s.body.name.toLowerCase();
|
||||
return name.includes(searchTerm);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const isSearchMatchFolder = (searchTerm: string, f: FolderUnion) => {
|
||||
if (f.name.toLowerCase().includes(searchTerm)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/** Given an input search term, returns folder IDs (number) and Sequence UUIDs
|
||||
* that match */
|
||||
export const searchFolderTree = (props: FolderSearchProps): FolderUnion[] => {
|
||||
// A sequence is included if:
|
||||
// * CASE 1: The name is a search match
|
||||
// * CASE 2: The containing folder is a search match.
|
||||
// A folder is included if:
|
||||
// * CASE 3: The name is a search match
|
||||
// * CASE 4: It contains a sequence that is a match.
|
||||
// * CASE 5: It has a child that has a search match.
|
||||
|
||||
const searchTerm = props.input.toLowerCase();
|
||||
const sequenceSet = new Set<string>();
|
||||
const folderSet = new Set<FolderUnion>();
|
||||
|
||||
props.root.folders.map(level1 => {
|
||||
level1.content.map(level1Sequence => { // ========= Level 1
|
||||
if (isSearchMatchSeq(searchTerm, props.references[level1Sequence])) {
|
||||
// CASE 1:
|
||||
sequenceSet.add(level1Sequence);
|
||||
// CASE 4:
|
||||
folderSet.add(level1);
|
||||
}
|
||||
});
|
||||
|
||||
if (isSearchMatchFolder(searchTerm, level1)) {
|
||||
// CASE 2
|
||||
level1.content.map(uuid => sequenceSet.add(uuid));
|
||||
// CASE 3
|
||||
folderSet.add(level1);
|
||||
}
|
||||
|
||||
level1.children.map(level2 => { // ================ LEVEL 2
|
||||
if (isSearchMatchFolder(searchTerm, level2)) {
|
||||
// CASE 2
|
||||
level2.content.map(uuid => sequenceSet.add(uuid));
|
||||
// CASE 3
|
||||
folderSet.add(level2);
|
||||
// CASE 5
|
||||
folderSet.add(level1);
|
||||
}
|
||||
|
||||
level2.content.map(level2Sequence => {
|
||||
if (isSearchMatchSeq(searchTerm, props.references[level2Sequence])) {
|
||||
// CASE 1:
|
||||
sequenceSet.add(level2Sequence);
|
||||
// CASE 4:
|
||||
folderSet.add(level2);
|
||||
// CASE 5
|
||||
folderSet.add(level1);
|
||||
}
|
||||
});
|
||||
level2.children.map(level3 => { // ============== LEVEL 3
|
||||
if (isSearchMatchFolder(searchTerm, level3)) {
|
||||
// CASE 2
|
||||
level3.content.map(uuid => sequenceSet.add(uuid));
|
||||
// CASE 3
|
||||
folderSet.add(level3);
|
||||
// CASE 5
|
||||
folderSet.add(level2);
|
||||
// CASE 5
|
||||
folderSet.add(level1);
|
||||
}
|
||||
level3.content.map(level3Sequence => {
|
||||
if (isSearchMatchSeq(searchTerm, props.references[level3Sequence])) {
|
||||
// CASE 1:
|
||||
sequenceSet.add(level3Sequence);
|
||||
// CASE 3
|
||||
folderSet.add(level3);
|
||||
// CASE 5
|
||||
folderSet.add(level2);
|
||||
// CASE 5
|
||||
folderSet.add(level1);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return Array.from(folderSet);
|
||||
};
|
|
@ -24,10 +24,8 @@ import { farmwareState } from "../farmware/reducer";
|
|||
import { initialState as regimenState } from "../regimens/reducer";
|
||||
import { initialState as sequenceState } from "../sequences/reducer";
|
||||
import { initialState as alertState } from "../messages/reducer";
|
||||
import {
|
||||
ingest,
|
||||
searchFoldersAndSequencesForTerm
|
||||
} from "../folders/data_transfer";
|
||||
import { ingest } from "../folders/data_transfer";
|
||||
import { searchFolderTree } from "../folders/search_folder_tree";
|
||||
|
||||
export const emptyState = (): RestResources => {
|
||||
return {
|
||||
|
@ -190,7 +188,7 @@ export const resourceReducer =
|
|||
.add<string | undefined>(Actions.FOLDER_SEARCH, (s, { payload }) => {
|
||||
s.index.sequenceFolders.searchTerm = payload;
|
||||
if (payload) {
|
||||
const folders = searchFoldersAndSequencesForTerm({
|
||||
const folders = searchFolderTree({
|
||||
references: s.index.references,
|
||||
input: payload,
|
||||
root: s.index.sequenceFolders.folders
|
||||
|
|
Loading…
Reference in New Issue