Skip to content

Commit

Permalink
Merge pull request #815 from MuckRock/798-portable-selection
Browse files Browse the repository at this point in the history
Derive selected documents from selected IDs
  • Loading branch information
eyeseast authored Nov 6, 2024
2 parents 1d568dd + b24df08 commit da7640c
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 21 deletions.
26 changes: 20 additions & 6 deletions src/lib/components/documents/ResultsList.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
<script context="module" lang="ts">
import { writable, type Writable } from "svelte/store";
import {
derived,
writable,
type Readable,
type Writable,
} from "svelte/store";
import type { Document, DocumentResults, Maybe } from "$lib/api/types";
// IDs might be strings or numbers, depending on the API endpoint
// enforce type consistency here to avoid comparison bugs later
export const selected: Writable<Document[]> = writable([]);
export let visible: Writable<Set<Document>> = writable(new Set());
export const visible: Writable<Map<string, Document>> = writable(new Map());
export const selectedIds: Writable<string[]> = writable([]);
export const selected: Readable<Document[]> = derived(
[visible, selectedIds],
([$visible, $selectedIds]) =>
$selectedIds.map((d) => $visible.get(d)).filter(Boolean) as Document[],
);
export let total: Writable<number> = writable(0);
</script>
Expand Down Expand Up @@ -35,7 +45,7 @@
const embed: boolean = getContext("embed");
// track what's visible so we can compare to $selected
$: $visible = new Set(results);
$: $visible = new Map(results.map((d) => [String(d.id), d]));
// load the next set of results
async function load(url: URL) {
Expand Down Expand Up @@ -99,7 +109,11 @@
{#if !embed}
<label>
<span class="sr-only">{$_("documents.select")}</span>
<input type="checkbox" bind:group={$selected} value={document} />
<input
type="checkbox"
bind:group={$selectedIds}
value={document.id}
/>
</label>
{/if}
<DocumentListItem {document} />
Expand All @@ -125,7 +139,7 @@
ghost
mode="primary"
disabled={loading}
on:click={(e) => {
on:click={() => {
if (next) load(new URL(next));
}}
>
Expand Down
30 changes: 27 additions & 3 deletions src/lib/components/documents/tests/ResultsList.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { DocumentResults } from "$lib/api/types";
import { describe, it, expect } from "vitest";
import { describe, it, expect, beforeEach } from "vitest";
import { get } from "svelte/store";
import { render, screen, fireEvent } from "@testing-library/svelte";

import ResultsList, { selected } from "../ResultsList.svelte";
import ResultsList, { selected, selectedIds } from "../ResultsList.svelte";
import searchResults from "@/test/fixtures/documents/search-highlight.json";

const results = searchResults as unknown as DocumentResults;
Expand All @@ -17,6 +17,10 @@ const empty: DocumentResults = {
};

describe("ResultsList", () => {
beforeEach(() => {
selectedIds.set([]);
});

it("shows results", () => {
render(ResultsList, {
results: results.results,
Expand All @@ -43,13 +47,33 @@ describe("ResultsList", () => {
expect(heading.textContent).toEqual("No search results");
});

it("populates $selected store", () => {
it("populates $selectedIds store", () => {
render(ResultsList, {
results: results.results,
count: results.count,
next: results.next,
});

expect(get(selectedIds)).toEqual([]);

const checkboxes = screen.getAllByRole("checkbox");

// check all the boxes
checkboxes.forEach(async (c) => {
await fireEvent.click(c);
});

expect(get(selectedIds)).toEqual(results.results.map((d) => String(d.id)));
});

it("builds the $selected store from the $selectedIds and $visible store", () => {
render(ResultsList, {
results: results.results,
count: results.count,
next: results.next,
});

expect(get(selectedIds)).toEqual([]);
expect(get(selected)).toEqual([]);

const checkboxes = screen.getAllByRole("checkbox");
Expand Down
8 changes: 4 additions & 4 deletions src/lib/components/forms/BulkActions.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Most actual actions are deferred to their own forms, so this is more of a switch
</script>

<script lang="ts">
import type { Writable } from "svelte/store";
import type { Readable } from "svelte/store";
import type { Document, Nullable } from "$lib/api/types";
import { getContext, type ComponentType } from "svelte";
Expand All @@ -23,8 +23,8 @@ Most actual actions are deferred to their own forms, so this is more of a switch
import Dropdown, {
type Placement,
} from "@/lib/components/common/Dropdown.svelte";
import Menu from "@/lib/components/common/Menu.svelte";
} from "$lib/components/common/Dropdown.svelte";
import Menu from "$lib/components/common/Menu.svelte";
import Modal from "../layouts/Modal.svelte";
import Portal from "../layouts/Portal.svelte";
import SidebarItem from "../sidebar/SidebarItem.svelte";
Expand All @@ -41,7 +41,7 @@ Most actual actions are deferred to their own forms, so this is more of a switch
export let position: Placement = "top-start";
const me = getCurrentUser();
const selected: Writable<Document[]> = getContext("selected");
const selected: Readable<Document[]> = getContext("selected");
const actions: Record<Action, string> = {
edit: $_("bulk.actions.edit"),
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/inputs/Selection.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<svelte:options accessors />

<script lang="ts">
import type { Writable } from "svelte/store";
import type { Readable } from "svelte/store";
import type { Document } from "$lib/api/types";
import { getContext } from "svelte";
Expand All @@ -12,7 +12,7 @@
export let query: undefined | string = undefined;
export let resultsCount: undefined | number = undefined;
const selected: Writable<Document[]> = getContext("selected");
const selected: Readable<Document[]> = getContext("selected");
// default to the first option, for convenience
let choice = [...documents][0];
Expand Down
5 changes: 3 additions & 2 deletions src/lib/components/layouts/AddOnLayout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import PageToolbar from "../common/PageToolbar.svelte";
import ResultsList, {
selected,
selectedIds,
total,
visible,
} from "../documents/ResultsList.svelte";
Expand Down Expand Up @@ -56,9 +57,9 @@
function selectAll(e) {
if (e.target.checked) {
$selected = [...$visible];
$selectedIds = [...$visible.keys()];
} else {
$selected = [];
$selectedIds = [];
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/lib/components/layouts/DocumentBrowser.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
// Document comopnents
import ResultsList, {
selected,
selectedIds,
total,
visible,
} from "$lib/components/documents/ResultsList.svelte";
Expand Down Expand Up @@ -103,9 +104,9 @@
function selectAll(e: Event) {
const target = e.target as HTMLInputElement;
if (target.checked) {
$selected = [...$visible];
$selectedIds = [...$visible.keys()];
} else {
$selected = [];
$selectedIds = [];
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/routes/(app)/documents/sidebar/Actions.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import type { Writable } from "svelte/store";
import type { Readable } from "svelte/store";
import type { Document } from "$lib/api/types";
import { getContext } from "svelte";
Expand All @@ -17,7 +17,7 @@
import Projects from "$lib/components/forms/Projects.svelte";
import Share from "$lib/components/documents/Share.svelte";
const selected: Writable<Document[]> = getContext("selected");
const selected: Readable<Document[]> = getContext("selected");
let edit = false;
let organize = false;
Expand Down

0 comments on commit da7640c

Please sign in to comment.