Skip to content

Commit

Permalink
add QueryLibrary reducer
Browse files Browse the repository at this point in the history
Signed-off-by: Mason Fish <mason@looky.cloud>
  • Loading branch information
Mason Fish committed Nov 24, 2020
1 parent c63d595 commit a086573
Show file tree
Hide file tree
Showing 9 changed files with 409 additions and 1 deletion.
38 changes: 38 additions & 0 deletions src/js/state/QueryLibrary/actions.ts
@@ -0,0 +1,38 @@
import {
Group,
QLIB_ADD_ITEM,
QLIB_EDIT_ITEM,
QLIB_MOVE_ITEM,
QLIB_REMOVE_ITEM,
QLIB_SET_ALL,
Query
} from "./types"

export default {
setAll: (rootGroup: Group): QLIB_SET_ALL => ({
type: "QLIB_SET_ALL",
rootGroup
}),
addItem: (item: Query | Group, groupPath: number[]): QLIB_ADD_ITEM => ({
type: "QLIB_ADD_ITEM",
item,
groupPath
}),
removeItem: (itemPath: number[]): QLIB_REMOVE_ITEM => ({
type: "QLIB_REMOVE_ITEM",
itemPath
}),
editItem: (item: Query | Group, itemPath: number[]): QLIB_EDIT_ITEM => ({
type: "QLIB_EDIT_ITEM",
item,
itemPath
}),
moveItem: (
srcItemPath: number[],
destItemPath: number[]
): QLIB_MOVE_ITEM => ({
type: "QLIB_MOVE_ITEM",
srcItemPath,
destItemPath
})
}
9 changes: 9 additions & 0 deletions src/js/state/QueryLibrary/index.ts
@@ -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/QueryLibrary/initial.ts
@@ -0,0 +1,9 @@
import {QueryLibraryState} from "./types"

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

export default init
91 changes: 91 additions & 0 deletions src/js/state/QueryLibrary/reducer.ts
@@ -0,0 +1,91 @@
import {Group, Query, QueryLibraryAction, QueryLibraryState} from "./types"
import produce from "immer"
import {get, set, initial, last} from "lodash"
import init from "./initial"

export default produce(
(draft: QueryLibraryState, action: QueryLibraryAction) => {
switch (action.type) {
case "QLIB_SET_ALL":
return action.rootGroup
case "QLIB_ADD_ITEM":
addItemToGroup(draft, action.groupPath, action.item)
return
case "QLIB_REMOVE_ITEM":
removeItemFromGroup(draft, action.itemPath)
return
case "QLIB_EDIT_ITEM":
if (!get(draft, toItemPath(action.itemPath), null)) return

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

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

const addItemToGroup = (
draft: QueryLibraryState,
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: QueryLibraryState,
itemPath: number[]
): void => {
const parentGroup = get(draft, toItemPath(initial(itemPath)), null)
if (!parentGroup) return

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

const moveItem = (
draft: QueryLibraryState,
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 (srcItemPath.length === destItemPath.length) {
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/QueryLibrary/selectors.ts
@@ -0,0 +1,6 @@
import {QueryLibraryState} from "./types"
import {State} from "../types"

export default {
getRaw: (state: State): QueryLibraryState => state.queryLibrary
}
201 changes: 201 additions & 0 deletions src/js/state/QueryLibrary/test.ts
@@ -0,0 +1,201 @@
import initTestStore from "../../test/initTestStore"
import QueryLibrary 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",
value: "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",
value: "testValue4",
tags: ["testTag2"]
}
]
},
{
// .items[0].items[2]
id: "testId5",
name: "testName5",
description: "testDescription5",
value: "testValue5",
tags: ["testTag1"]
}
]
}
]
}

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

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

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

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

const state = store.getState()

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

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

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

store.dispatch(QueryLibrary.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(QueryLibrary.setAll(testLib))

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

store.dispatch(QueryLibrary.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(QueryLibrary.setAll(testLib))

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

store.dispatch(QueryLibrary.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(QueryLibrary.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(QueryLibrary.setAll(testLib))

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

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

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

store.dispatch(QueryLibrary.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", () => {
store.dispatch(QueryLibrary.setAll(testLib))

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

const testName2Query = testName1Group[0]

// move to end
store.dispatch(QueryLibrary.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(QueryLibrary.moveItem([0, 2], [0, 0]))

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

test("move query, different group", () => {
store.dispatch(QueryLibrary.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(QueryLibrary.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(QueryLibrary.setAll(testLib))

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

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

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

0 comments on commit a086573

Please sign in to comment.