Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rigorous tests for the viewer's reorder function #241

Merged
merged 2 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions ui/src/components/viewer/viewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { Vow, PageAndRotOp, NodeType, BreadcrumbItemType, MovePagesBetweenD
import type { ThumbnailPageDroppedArgs, ShowDualButtonEnum } from 'types';
import type { DataTransferExtractedPages} from 'types';
import ErrorMessage from 'components/error_message';
import { reorder_pages } from 'utils/misc';
import { reorder as reorder_pages } from 'utils/array';
import { contains_every, uniq } from 'utils/array';

import { DATA_TRANSFER_EXTRACTED_PAGES } from 'cconstants';
Expand Down Expand Up @@ -130,11 +130,12 @@ export default function Viewer({
i.e we just reordering. It is so because all source pages (their IDs)
were found in the target document version.
*/
const new_pages = reorder_pages({
const new_pages = reorder_pages<PageAndRotOp, string>({
arr: pages!.data!,
source_ids: source_ids,
target_id: target_id,
position: position
position: position,
idf: (val: PageAndRotOp) => val.page.id
});
if (!new_pages.every((value, index) => value.page.id == pages!.data![index].page.id)) {
setUnappliedPagesOpChanges(true);
Expand Down
139 changes: 138 additions & 1 deletion ui/src/utils/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,142 @@ function uniq<T = string>(arr: T[]): T[] {
return [...new Set(arr)];
}

type ReorderArgs<T, K> = {
arr: T[];
source_ids: Array<K>;
target_id: K;
position: 'before' | 'after';
idf?: (val: T) => any;
}

/**
* Returns an array with reordered items.
*
* Items are reordered as follows: source_ids will be positioned
* before or after target_id (depending on positioned arg).
* Couple of examples.
* Example 1:
*
* arr = [ 1, 2, 3, 4 ]
* source_ids = [2]
* target_id = 4
* position = 'after'
*
* In other words, item 2 will be positioned after item 4.
* Result will be:
*
* result = [1, 3, 4, 2]
*
* Example 2:
*
* arr = [ 1, 2, 3, 4 ]
* source_ids = [2]
* target_id = 4
* position = 'before'

Result will be (element 2 will be positioned before element 4):

result = [1, 3, 2, 4]

Example 3:

arr = [1, 2]
source_ids = [2]
target_id = 1
position = 'before'

Result will be:

result = [2, 1]

Example 4:

arr = [1, 2]
source_ids = [2]
target_id = 1
position = 'after'

Result will be:

result = [1, 2]

i.e. same as input because source was already after target

Example 5:

arr = [1, 2, 3, 4, 5, 6]
source_ids = [1, 3]
target = 5

result: [2, 4, 5, 1, 3, 6]
*/
function reorder<T=number, K=string>({
arr,
source_ids,
target_id,
position,
idf
}: ReorderArgs<T, K>): T[] {

export {uniq, uniq_concat, subtract, overlap, contains_every}
if (!idf) {
idf = (x: T): T => x;
}

const arr_ids = arr.map(i => idf!(i))

if (!arr_ids.includes(target_id)) {
console.warn(`Target ID ${target_id} was not found in arr: `, arr);
return arr;
}

let result: T[] = [];
let insert_now = false;
const source: T[] = arr.filter(
i => source_ids.includes(idf!(i))
);

if (source.length == 0) {
throw new Error("Source list is empty. Cannot reorder.");
}

arr.forEach((item: T) => {
if (insert_now) {
result.push(...source);
insert_now = false;
}

if (!source_ids.includes(idf!(item)) && idf!(item) !== target_id) {
result.push(item);
} else if (idf!(item) === target_id) {
if (position == 'before') {
result.push(...source);
if (!source_ids.includes(idf!(item))) {
result.push(item);
}
} else {
insert_now = true; // will insert source on next iteration
if (!source_ids.includes(idf!(item))) {
result.push(item);
}
}
}
});

// is the case when target is last element of the array
// and we want to insert "after"
if (result.length < arr.length) {
result.push(...source);
}

return result;
}


export {
uniq,
uniq_concat,
subtract,
overlap,
contains_every,
reorder
}
107 changes: 0 additions & 107 deletions ui/src/utils/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,110 +73,3 @@ export function build_nodes_list_params({

return result;
}

type ReorderPagesArgs = {
arr: PageAndRotOp[];
source_ids: Array<string>;
target_id: string;
position: DroppedThumbnailPosition;
}

export function reorder_pages({
arr,
source_ids,
target_id,
position
}: ReorderPagesArgs): PageAndRotOp[] {
/*
Returns an array with reordered pages.

Items are reordered as follows: source_ids will be positioned
before or after target_id (depending on positioned arg).
Couple of examples.
Example 1:

arr = [ 1, 2, 3, 4 ]
source_ids = [2]
target_id = 4
position = 'after'

In other words, item 2 will be positioned after item 4.
Result will be:

result = [1, 3, 4, 2]

Example 2:

arr = [ 1, 2, 3, 4 ]
source_ids = [2]
target_id = 4
position = 'before'

Result will be (element 2 will be positioned before element 4):

result = [1, 3, 2, 4]

Example 3:
arr = [1, 2]
source_ids = [2]
target_id = 1
position = 'before'

Result will be:

result = [2, 1]

Example 4:

arr = [1, 2]
source_ids = [2]
target_id = 1
position = 'after'

Result will be:

result = [1, 2]

i.e. same as input because source was already after target

Example 5:

arr = [1, 2, 3, 4, 5, 6]
source_ids = [1, 3]
target = 5

result: [2, 4, 5, 1, 3, 6]
*/
let result: PageAndRotOp[] = [];
let insert_now = false;
const source: PageAndRotOp[] = arr.filter(i => source_ids.includes(i.page.id));

if (source.length == 0) {
throw new Error("Source list is empty. Cannot reorder.");
}

arr.forEach((item: PageAndRotOp) => {
if (insert_now) {
result.push(...source);
insert_now = false;
}

if (!source_ids.includes(item.page.id) && item.page.id !== target_id) {
result.push(item);
} else if (item.page.id === target_id) {
if (position == 'before') {
result.push(...source);
if (!source_ids.includes(item.page.id)) {
result.push(item);
}
} else {
insert_now = true; // will insert source on next iteration
if (!source_ids.includes(item.page.id)) {
result.push(item);
}
}
}
});

return result;
}
94 changes: 93 additions & 1 deletion ui/tests/array.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { subtract } from 'utils/array'
import { subtract, reorder } from 'utils/array'


type Node = {
Expand Down Expand Up @@ -60,4 +60,96 @@ describe('utils/array/subtract', () => {

});

describe('utils/array/reorder', () => {
test('basic data 1', () => {
const actual_result = reorder<number, number>({
arr: [1, 2, 3, 4],
source_ids: [2],
target_id: 4,
position: 'after'
});
const expect_result = [1, 3, 4, 2];

expect(actual_result).toEqual(expect_result);
});

test('basic data 2', () => {
const actual_result = reorder<number, number>({
arr: [1, 2, 3, 4],
source_ids: [2],
target_id: 4,
position: 'before'
});
const expect_result = [1, 3, 2, 4];

expect(actual_result).toEqual(expect_result);
});

test('basic data 3', () => {
const actual_result = reorder<number, number>({
arr: [1, 2],
source_ids: [2],
target_id: 1,
position: 'before'
});

expect(actual_result).toEqual([2, 1]);
});

test('basic data 4', () => {
const actual_result = reorder<number, number>({
arr: [1, 2],
source_ids: [2],
target_id: 1,
position: 'after'
});

expect(actual_result).toEqual([1, 2]);
});

test('basic data 5', () => {
const actual_result = reorder<number, number>({
arr: [1, 2, 3, 4, 5, 6],
source_ids: [1, 3],
target_id: 5,
position: 'after'
});

expect(actual_result).toEqual([2, 4, 5, 1, 3, 6]);
});

test('basic data 6', () => {
const actual_result = reorder<number, number>({
arr: [1, 2],
source_ids: [1],
target_id: 2,
position: 'after'
});

expect(actual_result).toEqual([2, 1]);
});

test('basic data 7', () => {
const actual_result = reorder<number, number>({
arr: [1, 2],
source_ids: [1],
target_id: 2,
position: 'before'
});

expect(actual_result).toEqual([1, 2]);
});

test('not existing target', () => {
const actual_result = reorder<number, number>({
arr: [1, 2, 3],
source_ids: [1],
target_id: 10,
position: 'before'
});

expect(actual_result).toEqual([1, 2, 3]);
});
})

export {}