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

Test that Qiskit TocGrouping is in sync with index page #1300

Merged
merged 2 commits into from
May 12, 2024
Merged
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
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);
});
Loading