-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Test that Qiskit TocGrouping is in sync with index page
- Loading branch information
1 parent
1da5a40
commit bf50ccc
Showing
1 changed file
with
132 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// 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 very `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._]+/; | ||
// Ex: 'Quantum information (qiskit.quantum_info)'. | ||
// Ex: '* [Quantum Circuits (`qiskit.circuit`)](circuit)' | ||
const match = text.match(/qiskit\.[a-zA-Z._]+/); | ||
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(); | ||
// TODO: check dev & figure out how to check future versions too | ||
const indexModuleGroups = await getIndexModuleGroups( | ||
"docs/api/qiskit/index.mdx", | ||
); | ||
const tocModuleGroups = await getTocModuleGroups("docs/api/qiskit/_toc.json"); | ||
expect(indexModuleGroups).toEqual(tocModuleGroups); | ||
}); |