Skip to content

Commit

Permalink
Test that Qiskit TocGrouping is in sync with index page (Qiskit#1300)
Browse files Browse the repository at this point in the history
The Qiskit devs were worried about the index page from
Qiskit/qiskit#12333 falling out of sync with our
custom grouping for `_toc.json`.

This PR adds a test to ensure that the two files are in sync. In
particular, it checks that the grouping and sequence of modules is
identical. It does not check that the label for the groups are the same,
though.

For now, we only check the dev docs because the latest docs (1.0) fail.
Qiskit#1341 will also start
checking the latest docs once 1.1 is released.
  • Loading branch information
Eric-Arellano authored May 12, 2024
1 parent d885d0a commit b357105
Showing 1 changed file with 133 additions and 0 deletions.
133 changes: 133 additions & 0 deletions scripts/lib/api/TocGrouping.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
// This code is a Qiskit project.
//
// (C) Copyright IBM 2024.
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE file in the root directory
// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
//
// Any modifications or derivative works of this code must retain this
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.

import { readFile } from "fs/promises";

import { expect, test } from "@jest/globals";

import { QISKIT_TOC_GROUPING } from "./TocGrouping";
import type { TocEntry } from "./generateToc";

/**
* The module names belonging to a section, e.g.
* `['qiskit.circuit', 'qiskit.circuit.library']`.
*
* For top-level modules, like `qiskit.quantum_info`, there will only
* be a single element.
* */
type ModuleGroup = string[];

/**
* Ensure our assumptions about Qiskit's TocGrouping are correct.
*
* These assumptions are what allow us to infer what is a top-level module
* (like 'qiskit.quantum_info') vs. a section (like 'Circuit construction').
*
* If these assumptions are getting in the way, you can rewrite these tests.
* A more robust approach would be to read the front-matter/metadata for the
* URLs to see if `python_api_type: module` is set. This is complicated by
* module pages sometimes being in 'Module overview' vs being on a standalone page
* like 'qiskit.circuit.singleton'.
*/
function validateTopLevelModuleAssumptions(): void {
for (const entry of QISKIT_TOC_GROUPING.entries) {
if (
entry.kind === "module" &&
!entry.title.includes("qiskit.") &&
entry.title !== "API index"
) {
throw new Error(
"Expected every top-level module of QISKIT_TOC_GROUPING to have the module name in " +
"its title, e.g. 'Quantum information (qiskit.quantum_info)'. This will break the " +
"tests in this file. Either add the module name to the title or rewrite these tests. " +
`Bad entry: ${entry.title}`,
);
} else if (entry.kind === "section" && entry.name.includes("qiskit.")) {
throw new Error(
"Expected every `section` of QISKIT_TOC_GROUPING.entries to not have 'qiskit.' in the " +
"name. This will break the tests in this file. Either remove the module name or " +
`rewrite these tests. Bad entry: ${entry.name}`,
);
}
}
}

function extractModuleName(text: string): string {
const re = /qiskit\.[a-zA-Z._0-9]+/;
// Ex: 'Quantum information (qiskit.quantum_info)'.
// Ex: '* [Quantum Circuits (`qiskit.circuit`)](circuit)'
const match = text.match(re);
if (!match) {
throw new Error(`Could not extract module from ${text}`);
}
return match[0];
}

/**
* Finds all groups of modules from the index page.
*
* Each group has a list of page titles with the module name in parantheses.
*/
async function getIndexModuleGroups(fp: string): Promise<ModuleGroup[]> {
const rawIndex = await readFile(fp, "utf-8");
const result: ModuleGroup[] = [];
let currentGroup: ModuleGroup = [];
for (const line of rawIndex.split("\n")) {
if (line.startsWith("* ")) {
if (line.includes("qiskit.")) {
const module = extractModuleName(line);
currentGroup.push(module);
}
continue;
} else if (currentGroup.length) {
result.push(currentGroup);
currentGroup = [];
}
}
return result;
}

async function getTocModuleGroups(fp: string): Promise<ModuleGroup[]> {
const rawToc = await readFile(fp, "utf-8");
const entries = JSON.parse(rawToc).children as TocEntry[];
const result: ModuleGroup[] = [];
for (const entry of entries) {
const isTopLevelModule = entry.title.includes("qiskit.");
if (isTopLevelModule) {
const moduleName = extractModuleName(entry.title);
result.push([moduleName]);
} else if (entry.children) {
// The modules inside a custom Section cannot be renamed, so they
// will have their title set as the module, e.g. `qiskit.circuit`.
const childrenModules = Array.from(
entry.children
.filter((child) => child.title.startsWith("qiskit."))
.map((child) => child.title),
);
if (childrenModules.length) {
result.push(childrenModules);
}
}
}
return result;
}

test("Qiskit ToC mirrors index page sections", async () => {
validateTopLevelModuleAssumptions();
const indexModuleGroups = await getIndexModuleGroups(
"docs/api/qiskit/dev/index.mdx",
);
const tocModuleGroups = await getTocModuleGroups(
"docs/api/qiskit/dev/_toc.json",
);
expect(indexModuleGroups).toEqual(tocModuleGroups);
});

0 comments on commit b357105

Please sign in to comment.