Skip to content

Commit

Permalink
QueryLibrary model and reducer (#1239)
Browse files Browse the repository at this point in the history
* add QueryLibrary reducer

Signed-off-by: Mason Fish <mason@looky.cloud>

* add uncle test

Signed-off-by: Mason Fish <mason@looky.cloud>

* adjust pr comments

Signed-off-by: Mason Fish <mason@looky.cloud>

* change queryLibrary to queries

Signed-off-by: Mason Fish <mason@looky.cloud>

Co-authored-by: Mason Fish <mason@looky.cloud>
  • Loading branch information
mason-fish and Mason Fish authored Dec 1, 2020
1 parent 706d5dc commit 7dc9730
Show file tree
Hide file tree
Showing 9 changed files with 417 additions and 1 deletion.
38 changes: 38 additions & 0 deletions src/js/state/Queries/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
Group,
QUERIES_ADD_ITEM,
QUERIES_EDIT_ITEM,
QUERIES_MOVE_ITEM,
QUERIES_REMOVE_ITEM,
QUERIES_SET_ALL,
Query
} from "./types"

export default {
setAll: (rootGroup: Group): QUERIES_SET_ALL => ({
type: "QUERIES_SET_ALL",
rootGroup
}),
addItem: (item: Query | Group, groupPath: number[]): QUERIES_ADD_ITEM => ({
type: "QUERIES_ADD_ITEM",
item,
groupPath
}),
removeItem: (itemPath: number[]): QUERIES_REMOVE_ITEM => ({
type: "QUERIES_REMOVE_ITEM",
itemPath
}),
editItem: (item: Query | Group, itemPath: number[]): QUERIES_EDIT_ITEM => ({
type: "QUERIES_EDIT_ITEM",
item,
itemPath
}),
moveItem: (
srcItemPath: number[],
destItemPath: number[]
): QUERIES_MOVE_ITEM => ({
type: "QUERIES_MOVE_ITEM",
srcItemPath,
destItemPath
})
}
9 changes: 9 additions & 0 deletions src/js/state/Queries/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import actions from "./actions"
import reducer from "./reducer"
import selectors from "./selectors"

export default {
...actions,
...selectors,
reducer
}
9 changes: 9 additions & 0 deletions src/js/state/Queries/initial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {QueriesState} from "./types"

const init = (): QueriesState => ({
id: "root",
name: "root",
items: []
})

export default init
85 changes: 85 additions & 0 deletions src/js/state/Queries/reducer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {Group, Query, QueriesAction, QueriesState} from "./types"
import produce from "immer"
import {get, set, initial, last, isEqual} from "lodash"
import init from "./initial"

export default produce((draft: QueriesState, action: QueriesAction) => {
switch (action.type) {
case "QUERIES_SET_ALL":
return action.rootGroup
case "QUERIES_ADD_ITEM":
addItemToGroup(draft, action.groupPath, action.item)
return
case "QUERIES_REMOVE_ITEM":
removeItemFromGroup(draft, action.itemPath)
return
case "QUERIES_EDIT_ITEM":
if (!get(draft, toItemPath(action.itemPath), null)) return

set(draft, toItemPath(action.itemPath), action.item)
return
case "QUERIES_MOVE_ITEM":
moveItem(draft, action.srcItemPath, action.destItemPath)
return
}
}, init())

const toItemPath = (path: number[]): string =>
path.map((pathNdx) => `items[${pathNdx}]`).join(".")

const addItemToGroup = (
draft: QueriesState,
groupPath: number[],
item: Query | Group,
index?: number
): void => {
const parentGroup = get(draft, toItemPath(groupPath), null)
if (!parentGroup) return

if (typeof index === "undefined") {
parentGroup.items.push(item)
return
}

parentGroup.items.splice(index, 0, item)
}

const removeItemFromGroup = (draft: QueriesState, itemPath: number[]): void => {
const parentGroup = get(draft, toItemPath(initial(itemPath)), null)
if (!parentGroup) return

parentGroup.items.splice(last(itemPath), 1)
}

const moveItem = (
draft: QueriesState,
srcItemPath: number[],
destItemPath: number[]
): void => {
const srcItem = get(draft, toItemPath(srcItemPath), null)

if (!srcItem) return
if (!get(draft, toItemPath(initial(destItemPath)), null)) return

// If the move is all in the same directory then the adjusting indices can
// cause an off by one issue since the destination index will be affected after
// removal (e.g. an item cannot be moved to the end of its current group because of this).
// For this situation we instead remove the item first, and then insert its copy
if (isEqual(initial(srcItemPath), initial(destItemPath))) {
removeItemFromGroup(draft, srcItemPath)
addItemToGroup(
draft,
initial(destItemPath),
{...srcItem},
last(destItemPath)
)
} else {
addItemToGroup(
draft,
initial(destItemPath),
{...srcItem},
last(destItemPath)
)
removeItemFromGroup(draft, srcItemPath)
}
}
6 changes: 6 additions & 0 deletions src/js/state/Queries/selectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {QueriesState} from "./types"
import {State} from "../types"

export default {
getRaw: (state: State): QueriesState => state.queries
}
215 changes: 215 additions & 0 deletions src/js/state/Queries/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
import initTestStore from "../../test/initTestStore"
import Queries from "./"
import {Group} from "./types"
import get from "lodash/get"
import {State} from "../types"

let store
beforeEach(() => {
store = initTestStore()
})

const testLib = {
id: "root",
name: "root",
items: [
{
// .items[0]
id: "testId1",
name: "testName1",
items: [
{
// .items[0].items[0]
id: "testId2",
name: "testName2",
description: "testDescription2",
zql: "testValue2",
tags: ["testTag1", "testTag2"]
},
{
// .items[0].items[1]
id: "testId3",
name: "testName3",
items: [
{
// .items[0].items[1].items[0]
id: "testId4",
name: "testName4",
description: "testDescription4",
zql: "testValue4",
tags: ["testTag2"]
}
]
},
{
// .items[0].items[2]
id: "testId5",
name: "testName5",
description: "testDescription5",
zql: "testValue5",
tags: ["testTag1"]
}
]
}
]
}

const newQuery = {
id: "newQueryId",
name: "newQueryName",
description: "newQueryDescription",
zql: "newQueryValue",
tags: []
}

const newGroup = {
id: "newGroupId",
name: "newGroupName",
items: []
}

const getGroup = (state: State, path: number[]): Group => {
return get(
Queries.getRaw(state),
path.map((pathNdx) => `items[${pathNdx}]`).join(".")
)
}

test("set all", () => {
store.dispatch(Queries.setAll(testLib))

const state = store.getState()

expect(Queries.getRaw(state)).toEqual(testLib)
})

test("add query", () => {
store.dispatch(Queries.setAll(testLib))

expect(getGroup(store.getState(), [0]).items).toHaveLength(3)

store.dispatch(Queries.addItem(newQuery, [0]))

expect(getGroup(store.getState(), [0]).items).toHaveLength(4)
expect(getGroup(store.getState(), [0]).items[3]).toEqual(newQuery)
})

test("add query, nested", () => {
store.dispatch(Queries.setAll(testLib))

expect(getGroup(store.getState(), [0, 1]).items).toHaveLength(1)

store.dispatch(Queries.addItem(newQuery, [0, 1]))

expect(getGroup(store.getState(), [0, 1]).items).toHaveLength(2)
expect(getGroup(store.getState(), [0, 1]).items[1]).toEqual(newQuery)
})

test("add group, add query to new group", () => {
store.dispatch(Queries.setAll(testLib))

expect(getGroup(store.getState(), [0]).items).toHaveLength(3)

store.dispatch(Queries.addItem(newGroup, [0]))

expect(getGroup(store.getState(), [0]).items).toHaveLength(4)
expect(getGroup(store.getState(), [0]).items[3]).toEqual(newGroup)
expect(getGroup(store.getState(), [0, 3]).items).toHaveLength(0)

store.dispatch(Queries.addItem(newQuery, [0, 3]))

expect(getGroup(store.getState(), [0, 3]).items).toHaveLength(1)
expect(getGroup(store.getState(), [0, 3]).items[0]).toEqual(newQuery)
})

test("remove query, group", () => {
store.dispatch(Queries.setAll(testLib))

const testName1Group = getGroup(store.getState(), [0]).items
expect(testName1Group).toHaveLength(3)

store.dispatch(Queries.removeItem([0, 0]))

expect(getGroup(store.getState(), [0]).items).toHaveLength(2)
expect(getGroup(store.getState(), [0]).items).toEqual(testName1Group.slice(1))

store.dispatch(Queries.removeItem([0, 0]))
expect(getGroup(store.getState(), [0]).items).toHaveLength(1)
expect(getGroup(store.getState(), [0]).items).toEqual([testName1Group[2]])
})

test("move query, same group, different group same depth", () => {
store.dispatch(Queries.setAll(testLib))

const testName1Group = getGroup(store.getState(), [0]).items
expect(testName1Group).toHaveLength(3)

const testName2Query = testName1Group[0]

// move to end
store.dispatch(Queries.moveItem([0, 0], [0, 2]))

expect(getGroup(store.getState(), [0]).items).toHaveLength(3)

expect(getGroup(store.getState(), [0]).items).toEqual([
...testName1Group.slice(1),
testName2Query
])

// move back to beginning
store.dispatch(Queries.moveItem([0, 2], [0, 0]))

expect(getGroup(store.getState(), [0]).items).toHaveLength(3)
expect(getGroup(store.getState(), [0]).items).toEqual(testName1Group)

// move to "uncle's" group
store.dispatch(Queries.addItem(newGroup, [0]))

expect(getGroup(store.getState(), [0, 1]).items).toHaveLength(1)
expect(getGroup(store.getState(), [0, 3]).items).toHaveLength(0)

const testName4Query = getGroup(store.getState(), [0, 1]).items[0]

store.dispatch(Queries.moveItem([0, 1, 0], [0, 3, 0]))

expect(getGroup(store.getState(), [0, 1]).items).toHaveLength(0)
expect(getGroup(store.getState(), [0, 3]).items).toHaveLength(1)
expect(getGroup(store.getState(), [0, 3]).items[0]).toEqual(testName4Query)
})

test("move query, different group", () => {
store.dispatch(Queries.setAll(testLib))

const testName1Group = getGroup(store.getState(), [0]).items
const testName3Group = (testName1Group[1] as Group).items

expect(testName1Group).toHaveLength(3)
expect(testName3Group).toHaveLength(1)

const testName2Query = testName1Group[0]

store.dispatch(Queries.moveItem([0, 0], [0, 1, 0]))

const newTestName1Group = getGroup(store.getState(), [0]).items
const newTestName3Group = (newTestName1Group[0] as Group).items

expect(newTestName1Group).toHaveLength(2)
expect(newTestName3Group).toHaveLength(2)

expect(newTestName1Group[0].id).toEqual(testName1Group[1].id)
expect(newTestName3Group).toEqual([testName2Query, ...testName3Group])
})

test("edit query", () => {
store.dispatch(Queries.setAll(testLib))

store.dispatch(Queries.editItem(newQuery, [0, 0]))
expect(getGroup(store.getState(), [0]).items[0]).toEqual(newQuery)
})

test("edit group", () => {
store.dispatch(Queries.setAll(testLib))

store.dispatch(Queries.editItem(newGroup, [0, 1]))
expect(getGroup(store.getState(), [0, 1])).toEqual(newGroup)
})
Loading

0 comments on commit 7dc9730

Please sign in to comment.