it("selectNone() should alter all pipelines", () => {
    const all = { group1: "abc".split(""), group2: "def".split("") };
    const sel = _st({ a: true, b: true, c: true, d: true, e: false, f: false });

    const model = new PipelineListVM(all, sel);
    model.selectNone();

    expect(model.displayedList().group1.selected()).toBe(false);
    expect(model.displayedList().group2.selected()).toBe(false);
    expect(_.every(sel, (s) => !s())).toBe(true);
  });
  it("automatically calculates group selection state", () => {
    const all = { group1: "abc".split(""), group2: "def".split("") };
    const sel = _st({ a: true, b: true, c: true, d: false, e: true, f: false });

    const model = new PipelineListVM(all, sel);
    expect(model.displayedList().group1.selected()).toBe(true);
    expect(model.displayedList().group2.selected()).toBe(false);

    sel.b(false);
    expect(model.displayedList().group1.selected()).toBe(false);
  });
  it("displays all pipelines if no search term", () => {
    const all = { group1: "abc".split(""), group2: "def".split("") };
    const sel = _st({ a: true, b: true, c: true, d: false, e: true, f: false });
    const model = new PipelineListVM(all, sel);

    const names = getPipelineNames(model.displayedList());
    expect(names).toEqual(("abcdef").split(""));
  });
  it("only displays pipelines that include the search term matching case insensitively", () => {
    const all = { group1: "abc".split(""), group2: "ab,cd".split(",") };
    const sel = _st({ a: true, b: true, c: true, ab: true, cd: false });
    const model = new PipelineListVM(all, sel);
    model.searchTerm("B");

    const names = getPipelineNames(model.displayedList());
    expect(names).toEqual(["ab", "b"]);
  });
  it("toggling the selected attribute on a pipeline entry alters the internal selection map", () => {
    const all = { group1: "abc".split(""), group2: "def".split("") };
    const sel = _st({ a: true, b: true, c: true, d: true, e: false, f: false });

    const model = new PipelineListVM(all, sel);
    expect(sel.f()).toBe(false);

    const pipes = model.displayedList().group2.pipelines;
    expect(pipes[2].name).toBe("f");
    check(pipes[2].selected); // should flip the specified state, expects this to be the previous state before onchange()

    expect(sel.f()).toBe(true);
  });
  it("selecting at least one pipeline should set group indeterminate state", () => {
    const all = { group1: "abc".split(""), group2: "def".split(""), group3: "g" };
    const sel = _st({ a: true, b: false, c: false, d: true, e: true, f: true, g: false });

    const model = new PipelineListVM(all, sel);
    const list = model.displayedList();

    expect(list.group1.indeterminate()).toBe(true);
    expect(list.group1.selected()).toBe(false);

    expect(list.group2.indeterminate()).toBe(false);
    expect(list.group2.selected()).toBe(true);

    expect(list.group3.indeterminate()).toBe(false);
    expect(list.group3.selected()).toBe(false);
  });
  it("restores prior expand/collapse state after clearing search", () => {
    const all = { group1: "abc".split(""), group2: "ab,cd".split(","), group3: "de".split("") };
    const sel = _st({ a: true, b: true, c: true, ab: true, cd: false, d: false, e: false });
    const model = new PipelineListVM(all, sel);

    expect(collectExpandedGroups(model.displayedList())).toEqual([]); // all collapsed by default

    // expand group2
    model.displayedList().group2.expanded(true);
    expect(collectExpandedGroups(model.displayedList())).toEqual(["group2"]);

    // search term should match both groups
    model.searchTerm("a");
    expect(collectExpandedGroups(model.displayedList())).toEqual(["group1", "group2"]); // all matches expanded by default in search

    // collapse group2 while in search results
    model.displayedList().group2.expanded(false);
    expect(collectExpandedGroups(model.displayedList())).toEqual(["group1"]); // should honor expand/collapse during search

    // clear search term
    model.searchTerm("");
    expect(collectExpandedGroups(model.displayedList())).toEqual(["group2"]); // should restore expand/collapse state to that of prior to search
  });