diff --git a/.changeset/little-light-bulb.md b/.changeset/little-light-bulb.md new file mode 100644 index 00000000..798b4d96 --- /dev/null +++ b/.changeset/little-light-bulb.md @@ -0,0 +1,6 @@ +--- +"@nodesecure/vis-network": minor +--- + +Visualize highlited packages with dashed yellow border + \ No newline at end of file diff --git a/README.md b/README.md index f0e81faf..bb40f9bb 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ Type a package name directly to search, or prefix with a filter name followed by - `ext` — file extension present in the package (e.g. `.js`, `.ts`). - `builtin` — Node.js core module used by the package (e.g. `fs`, `path`). - `size` — size range (see [size-satisfies](https://github.com/NodeSecure/size-satisfies#usage-example), e.g. `>50kb`, `10kb..200kb`). +- `highlighted` — all highlighted packages by default. ## FAQ diff --git a/i18n/arabic.js b/i18n/arabic.js index ae8889dd..7375d722 100644 --- a/i18n/arabic.js +++ b/i18n/arabic.js @@ -243,7 +243,8 @@ const ui = { legend: { default: "الحزمة بخير.", warn: "الحزمة بها تحذيرات.", - friendly: "الحزمة تتم صيانتها بواسطة نفس مؤلفي الحزمة الجذرية." + friendly: "الحزمة تتم صيانتها بواسطة نفس مؤلفي الحزمة الجذرية.", + highlighted: "الحزمة جزء من الحزم المميزة" }, lockedNavigation: { next: "التالي", diff --git a/i18n/english.js b/i18n/english.js index 01f5eaa8..3934ba82 100644 --- a/i18n/english.js +++ b/i18n/english.js @@ -315,13 +315,15 @@ const ui = { author: "name or email", ext: "file extension", builtin: "node.js module", - size: "e.g. >50kb" + size: "e.g. >50kb", + highlighted: "all" } }, legend: { default: "The package is fine.", warn: "The package has warnings.", - friendly: "The package is maintained by the same authors as the root package." + friendly: "The package is maintained by the same authors as the root package.", + highlighted: "The package is part of highlighted packages" }, lockedNavigation: { next: "Next", diff --git a/i18n/french.js b/i18n/french.js index 95cf6d33..73cf4a80 100644 --- a/i18n/french.js +++ b/i18n/french.js @@ -315,13 +315,15 @@ const ui = { author: "nom ou email", ext: "extension de fichier", builtin: "module node.js", - size: "ex. >50kb" + size: "ex. >50kb", + highlighted: "all" } }, legend: { default: "Rien à signaler.", warn: "La dépendance contient des menaces.", - friendly: "La dépendance est maintenu par des auteurs du package principal." + friendly: "La dépendance est maintenu par des auteurs du package principal.", + highlighted: "Le package fait partie des packages mis en évidence" }, lockedNavigation: { next: "Suivant", diff --git a/i18n/turkish.js b/i18n/turkish.js index 4365a215..52ccd603 100644 --- a/i18n/turkish.js +++ b/i18n/turkish.js @@ -245,7 +245,8 @@ const ui = { legend: { default: "Paket sorunsuz.", warn: "Pakette uyarılar var.", - friendly: "Paket, kök paketin yazarlarıyla aynı kişiler tarafından bakılmaktadır." + friendly: "Paket, kök paketin yazarlarıyla aynı kişiler tarafından bakılmaktadır.", + highlighted: "Paket, vurgulanan paketlerin bir parçasıdır" }, lockedNavigation: { next: "Sonraki", diff --git a/public/components/command-palette/command-palette.js b/public/components/command-palette/command-palette.js index 0c9a9d70..a99f899c 100644 --- a/public/components/command-palette/command-palette.js +++ b/public/components/command-palette/command-palette.js @@ -11,6 +11,7 @@ import { FILTER_HAS_HELPERS, FILTER_MULTI_SELECT, PRESETS, + FILTER_INSTANT_CONFIRM, computeMatches, getHelperValues } from "./filters.js"; @@ -94,12 +95,13 @@ class CommandPalette extends LitElement { #init = ({ detail: { linker, packages, network } }) => { this.#linker = linker; this.#network = network; - this.#packages = packages.map(({ id, name, version, flags }) => { + this.#packages = packages.map(({ id, name, version, flags, isHighlighted }) => { return { id: String(id), name, version, - flags + flags, + isHighlighted }; }); }; @@ -323,10 +325,15 @@ class CommandPalette extends LitElement { #selectHelper(helper) { if (helper.type === "filter") { - this.inputValue = `${helper.value}:`; - this.activeFilter = helper.value; - this.selectedIndex = -1; - this.results = []; + if (FILTER_INSTANT_CONFIRM.has(helper.value)) { + this.#addQuery(helper.value, "all"); + } + else { + this.inputValue = `${helper.value}:`; + this.activeFilter = helper.value; + this.selectedIndex = -1; + this.results = []; + } } else { this.#addQuery(this.activeFilter, helper.value); diff --git a/public/components/command-palette/filters.js b/public/components/command-palette/filters.js index 36e77e20..ff3b7618 100644 --- a/public/components/command-palette/filters.js +++ b/public/components/command-palette/filters.js @@ -20,7 +20,7 @@ export const VERSION_PRESETS = [ { label: "≥ 1.0", value: ">=1.0.0" }, { label: "< 1.0", value: "<1.0.0" } ]; -export const FILTERS_NAME = new Set(["package", "version", "flag", "license", "author", "ext", "builtin", "size"]); +export const FILTERS_NAME = new Set(["package", "version", "flag", "license", "author", "ext", "builtin", "size", "highlighted"]); export const PRESETS = [ { id: "has_vulnerabilities", filter: "flag", value: "hasVulnerabilities" }, { id: "has_scripts", filter: "flag", value: "hasScript" }, @@ -32,6 +32,8 @@ export const PRESETS = [ export const FILTER_HAS_HELPERS = new Set(["license", "ext", "builtin", "author"]); // Filters where the mode persists after selection (multi-select) export const FILTER_MULTI_SELECT = new Set(["flag"]); +// Filters that auto-confirm immediately on selection (no text input needed) +export const FILTER_INSTANT_CONFIRM = new Set(["highlighted"]); /** * Returns per-flag package counts across the full linker. @@ -243,6 +245,8 @@ function matchesFilter(opt, filterName, inputValue) { } case "flag": return opt.flags.includes(inputValue); + case "highlighted": + return inputValue === "all" ? opt.isHighlighted === true : opt.isHighlighted !== true; default: return false; } diff --git a/public/components/legend/legend.js b/public/components/legend/legend.js index 46cb9b1d..0a4d28f9 100644 --- a/public/components/legend/legend.js +++ b/public/components/legend/legend.js @@ -90,6 +90,7 @@ class Legend extends LitElement { ${this.#createLegendBoxElement(colors.WARN, legend.warn)} ${this.#createLegendBoxElement(colors.FRIENDLY, legend.friendly)} ${this.#createLegendBoxElement(colors.DEFAULT, legend.default)} + ${this.#createLegendBoxElement(colors.HIGHLIGHTED, legend.highlighted)} `; } @@ -98,7 +99,7 @@ class Legend extends LitElement { theme, text ) { - const style = `background-color: ${theme.color}; color: ${theme.font.color};`; + const style = `background-color: ${theme.color}; color: ${(theme.font ?? COLORS.LIGHT.DEFAULT.font).color};`; return html`
diff --git a/public/components/views/home/maintainers/maintainers.js b/public/components/views/home/maintainers/maintainers.js index 0c143b7d..59e261d9 100644 --- a/public/components/views/home/maintainers/maintainers.js +++ b/public/components/views/home/maintainers/maintainers.js @@ -238,7 +238,7 @@ width: 16px; const { packages, email, url = null, npmAvatar } = data; const personClasses = { person: true, - highlighted: this.secureDataSet.isHighlighted(data) + highlighted: this.secureDataSet.isHighlightedContact(data) }; return html` @@ -289,9 +289,9 @@ width: 16px; #highlightContacts(authors) { const highlightedAuthors = authors - .filter(([_, contact]) => this.secureDataSet.isHighlighted(contact)); + .filter(([_, contact]) => this.secureDataSet.isHighlightedContact(contact)); - const authorsRest = authors.filter(([_, contact]) => !this.secureDataSet.isHighlighted(contact)); + const authorsRest = authors.filter(([_, contact]) => !this.secureDataSet.isHighlightedContact(contact)); return [...highlightedAuthors, ...authorsRest]; } diff --git a/public/main.js b/public/main.js index 88392420..604cf8fa 100644 --- a/public/main.js +++ b/public/main.js @@ -488,12 +488,7 @@ function onSettingsSaved(defaultConfig = null) { window.settings.config.theme = theme; window.settings.config.disableExternalRequests = config.disableExternalRequests; - if (theme === "dark") { - document.body.classList.add("dark"); - } - else { - document.body.classList.remove("dark"); - } + document.body.classList.toggle("dark", theme === "dark"); await secureDataSet.init( secureDataSet.data, diff --git a/test/e2e/command-palette.spec.js b/test/e2e/command-palette.spec.js index 76e0a4b1..2d56d694 100644 --- a/test/e2e/command-palette.spec.js +++ b/test/e2e/command-palette.spec.js @@ -15,7 +15,7 @@ test.describe("[command-palette] presets and actions", () => { return window.i18n[activeLang].search_command; }); - await page.locator("body").click(); + await page.locator(`[data-menu="network--view"].active`).click(); await page.keyboard.press("Control+k"); await expect(page.locator(".backdrop")).toBeVisible(); @@ -105,7 +105,7 @@ test.describe("[command-palette] ignore flags and warnings", () => { return window.i18n[activeLang].search_command; }); - await page.locator("body").click(); + await page.locator(`[data-menu="network--view"].active`).click(); await page.keyboard.press("Control+k"); await expect(page.locator(".backdrop")).toBeVisible(); diff --git a/test/ui/command-palette-filters.test.js b/test/ui/command-palette-filters.test.js index d8c623ed..9a5d6766 100644 --- a/test/ui/command-palette-filters.test.js +++ b/test/ui/command-palette-filters.test.js @@ -18,7 +18,8 @@ const kLinker = new Map([ uniqueLicenseIds: ["MIT"], composition: { extensions: [".js", ".ts"], required_nodejs: ["fs", "path"] }, author: { name: "TJ Holowaychuk" }, - size: 102_400 + size: 102_400, + isHighlighted: true }], [1, { name: "lodash", @@ -27,7 +28,8 @@ const kLinker = new Map([ uniqueLicenseIds: ["MIT", "ISC"], composition: { extensions: [".js", ""], required_nodejs: ["path"] }, author: "John-David Dalton", - size: 5_000 + size: 5_000, + isHighlighted: true }], [2, { name: "semver", @@ -232,6 +234,20 @@ describe("computeMatches", () => { assert.deepEqual(result, new Set()); }); }); + + describe("filter: highlighted", () => { + it("should match only highlighted packages when value is 'all'", () => { + const result = computeMatches(kLinker, "highlighted", "all"); + + assert.deepEqual(result, new Set(["0", "1"])); + }); + + it("should match non-highlighted packages when value is not 'all'", () => { + const result = computeMatches(kLinker, "highlighted", "none"); + + assert.deepEqual(result, new Set(["2"])); + }); + }); }); describe("getFlagCounts", () => { diff --git a/workspaces/vis-network/src/constants.ts b/workspaces/vis-network/src/constants.ts index 23560ca3..1052237d 100644 --- a/workspaces/vis-network/src/constants.ts +++ b/workspaces/vis-network/src/constants.ts @@ -9,6 +9,7 @@ export type Color = { color: string; + border?: string; font: { color: string; background?: string; @@ -18,9 +19,9 @@ export type Color = { export const COLORS = Object.freeze({ LIGHT: { SELECTED: { - color: "#4527A0", + color: "#BFC5E0", font: { - color: "#FFF" + color: "#443730" } }, SELECTED_GROUP: { @@ -36,23 +37,29 @@ export const COLORS = Object.freeze({ } }, DEFAULT: { - color: "#E3F2FD", + color: "#BEE7E8", font: { color: "#121533" } }, WARN: { - color: "#EF5350", + color: "#FFBFA0", font: { - color: "#FFF" + color: "#6B2737" } }, FRIENDLY: { - color: "#e3fde3", + color: "#EDEEC0", font: { color: "#0e4522" } }, + HIGHLIGHTED: { + border: "#EA9010", + color: "", + font: { color: "" }, + margin: 12 + }, CONNECTED_IN: { color: "#C8E6C9", font: { @@ -109,6 +116,11 @@ export const COLORS = Object.freeze({ color: "#FFF" } }, + HIGHLIGHTED: { + border: "#dec42c", + color: "", + font: { color: "" } + }, CONNECTED_IN: { color: "rgb(89, 44, 109)", font: { diff --git a/workspaces/vis-network/src/dataset.ts b/workspaces/vis-network/src/dataset.ts index 3c22c74e..52895ce4 100644 --- a/workspaces/vis-network/src/dataset.ts +++ b/workspaces/vis-network/src/dataset.ts @@ -13,6 +13,7 @@ import type { // Import Internal Dependencies import * as utils from "./utils.ts"; +import type { VisEdge, VisNode } from "./network.ts"; declare global { interface Window { @@ -43,6 +44,7 @@ export type LinkerEntry = DependencyVersion & { hidden: boolean; hasWarnings: boolean; isFriendly: boolean; + isHighlighted: boolean; }; export interface PackageInfo { @@ -53,36 +55,16 @@ export interface PackageInfo { flags: string; links: DependencyLinks | undefined; isFriendly: boolean; + isHighlighted: boolean; } export type AuthorInfo = Maintainer & { packages: Set; }; -export interface VisNode { - id: number; - label: string; - color: string; - font: { - color: string; - background?: string; - multi: string; - }; - hidden?: boolean; -} - -export interface VisEdge { - id?: string | number; - from: number; - to: number; - label?: string; - font?: { - background: string; - }; -} - export default class NodeSecureDataSet extends EventTarget { #highligthedContacts!: HighlightedContacts; + #highlightedPackages; flagsToIgnore: Set; warningsToIgnore: Set; @@ -183,6 +165,8 @@ export default class NodeSecureDataSet extends EventTarget { return acc; }, { names: new Set(), emails: new Set() }); + this.#highlightedPackages = new Set(data.highlighted.packages); + const dependencies = Object.entries(data.dependencies); this.dependenciesCount = dependencies.length; @@ -244,6 +228,8 @@ export default class NodeSecureDataSet extends EventTarget { }) ); + const isHighlighted = this.#isHighlightedPackage(packageName, currVersion); + this.packages.push({ id, name: packageName, @@ -251,7 +237,8 @@ export default class NodeSecureDataSet extends EventTarget { hasWarnings, flags: flagStr.replace(/\s/g, ""), links, - isFriendly + isFriendly, + isHighlighted }); const label = `${packageName}@${currVersion}${flagStr}\n[${prettyBytes(size)}]`; @@ -259,6 +246,7 @@ export default class NodeSecureDataSet extends EventTarget { id, hasWarnings, isFriendly, + isHighlighted, theme: this.theme.toUpperCase() }); @@ -268,14 +256,17 @@ export default class NodeSecureDataSet extends EventTarget { version: currVersion, hidden: false, hasWarnings, - isFriendly + isFriendly, + isHighlighted }; this.linker.set(Number(id), linkerEntry); + const { color: nodeColor, font: nodeFont, ...colorRest } = color; this.rawNodesData.push({ id, label, - color: color.color, - font: { ...color.font, multi: "html" } + color: nodeColor, + font: { ...nodeFont, multi: "html" }, + ...colorRest }); for (const [depName, depVersion] of Object.entries(usedBy)) { @@ -339,13 +330,17 @@ export default class NodeSecureDataSet extends EventTarget { return { nodes, edges }; } - isHighlighted( + isHighlightedContact( contact: { name?: string; email?: string; } ): boolean { return this.#highligthedContacts.names.has(contact.name ?? "") || this.#highligthedContacts.emails.has(contact.email ?? ""); } + #isHighlightedPackage(name: string, version: string): boolean { + return this.#highlightedPackages.has(name) || this.#highlightedPackages.has(`${name}@${version}`); + } + findPackagesByName( name: string ): PackageInfo[] { diff --git a/workspaces/vis-network/src/index.ts b/workspaces/vis-network/src/index.ts index a616a375..3a69c5fd 100644 --- a/workspaces/vis-network/src/index.ts +++ b/workspaces/vis-network/src/index.ts @@ -12,11 +12,14 @@ export type { LinkerEntry, PackageInfo, AuthorInfo, - VisNode, - VisEdge, Contributor } from "./dataset.ts"; +export type { + VisNode, + VisEdge +} from "./network.ts"; + export { getJSON, getFlagsEmojisInlined, diff --git a/workspaces/vis-network/src/network.ts b/workspaces/vis-network/src/network.ts index d18cf226..525109dd 100644 --- a/workspaces/vis-network/src/network.ts +++ b/workspaces/vis-network/src/network.ts @@ -8,9 +8,7 @@ import * as CONSTANTS from "./constants.ts"; import * as utils from "./utils.ts"; import type NodeSecureDataSet from "./dataset.ts"; import type { - LinkerEntry, - VisNode, - VisEdge + LinkerEntry } from "./dataset.ts"; import type { I18n } from "./constants.ts"; @@ -25,7 +23,7 @@ export const NETWORK_OPTIONS = { vadjust: 2, size: 40 }, - margin: 20, + margin: 35, shadow: { enabled: true, color: "rgba(20, 20, 20, 0.1)" @@ -81,6 +79,37 @@ interface NetworkClickParams { }; } +interface VisAdvancedColor { + border?: string; + background?: string; +} + +export interface VisNode { + id: number; + label: string; + color: string | VisAdvancedColor; + border?: string; + borderWidth?: number; + font: { + color: string; + background?: string; + multi: string; + }; + hidden?: boolean; + shadow?: { enabled: boolean; }; + shapeProperties?: { borderDashes: number[] | boolean; }; +} + +export interface VisEdge { + id?: string | number; + from: number; + to: number; + label?: string; + font?: { + background: string; + }; +} + type ColorPalette = (typeof CONSTANTS.COLORS)[keyof typeof CONSTANTS.COLORS]; export default class NodeSecureNetwork { @@ -263,6 +292,12 @@ export default class NodeSecureNetwork { this.colors.HARDTOREAD; Object.assign(node, color); + + const entry = this.linker.get(Number(node.id)); + if (entry?.isHighlighted) { + node.borderWidth = 1; + node.shapeProperties = { borderDashes: false }; + } } for (const nodeId of nodeIdsToHighlight) { @@ -310,9 +345,9 @@ export default class NodeSecureNetwork { this.highlightEnabled = false; for (const node of allNodes) { - const { id, hasWarnings, isFriendly } = this.linker.get(Number(node.id))!; + const { id, hasWarnings, isFriendly, isHighlighted } = this.linker.get(Number(node.id))!; - Object.assign(node, utils.getNodeColor({ id, hasWarnings, theme: this.theme, isFriendly })); + Object.assign(node, utils.getNodeColor({ id, hasWarnings, theme: this.theme, isFriendly, isHighlighted })); } this.lastHighlightedIds = null; @@ -363,6 +398,34 @@ export default class NodeSecureNetwork { } } + /** + * Returns the selected color for a node, preserving highlighted border+shadow if applicable. + * @param {number} nodeId + * @param {"SELECTED" | "SELECTED_LOCK"} colorKey + */ + #selectedColor(nodeId: number, colorKey: "SELECTED" | "SELECTED_LOCK") { + const entry = this.linker.get(Number(nodeId)); + const base = this.colors[colorKey]; + + if (!entry?.isHighlighted) { + return base; + } + + const borderColor = CONSTANTS.COLORS[this.theme].HIGHLIGHTED.border; + + return { + color: { + background: base.color, + border: borderColor + }, + font: base.font, + borderWidth: 3, + shapeProperties: { + borderDashes: [6, 3] + } + }; + } + lockedNeighbourHighlight( params: NetworkClickParams | undefined ): boolean { @@ -386,7 +449,7 @@ export default class NodeSecureNetwork { } const color = node.id === selectedNode ? - this.colors.SELECTED_LOCK : + this.#selectedColor(node.id, "SELECTED_LOCK") : this.colors.SELECTED_GROUP; Object.assign(node, color); @@ -448,6 +511,13 @@ export default class NodeSecureNetwork { // mark all nodes as hard to read. for (const node of Object.values(allNodes)) { Object.assign(node, this.colors.HARDTOREAD); + + const entry = this.linker.get(Number(node.id)); + + if (entry?.isHighlighted) { + node.borderWidth = 1; + node.shapeProperties = { borderDashes: false }; + } } // get the second degree nodes @@ -471,7 +541,7 @@ export default class NodeSecureNetwork { } // the main node gets its own color and its label back. - Object.assign(allNodes[selectedNode], this.colors.SELECTED); + Object.assign(allNodes[selectedNode], this.#selectedColor(selectedNode, "SELECTED")); // select and label edges connected to the selected node const connectedEdges = this.network.getConnectedEdges(selectedNode); @@ -496,9 +566,9 @@ export default class NodeSecureNetwork { else if (this.highlightEnabled) { this.highlightEnabled = false; for (const node of Object.values(allNodes)) { - const { id, hasWarnings, isFriendly } = this.linker.get(Number(node.id))!; + const { id, hasWarnings, isFriendly, isHighlighted } = this.linker.get(Number(node.id))!; - Object.assign(node, utils.getNodeColor({ id, hasWarnings, theme: this.theme, isFriendly })); + Object.assign(node, utils.getNodeColor({ id, hasWarnings, theme: this.theme, isFriendly, isHighlighted })); } } diff --git a/workspaces/vis-network/src/types.ts b/workspaces/vis-network/src/types.ts new file mode 100644 index 00000000..e69de29b diff --git a/workspaces/vis-network/src/utils.ts b/workspaces/vis-network/src/utils.ts index 2397599c..0f224b33 100644 --- a/workspaces/vis-network/src/utils.ts +++ b/workspaces/vis-network/src/utils.ts @@ -2,7 +2,7 @@ import { getManifestEmoji } from "@nodesecure/flags/web"; // Import Internal Dependencies -import * as CONSTANTS from "./constants.ts"; +import { COLORS, type Color } from "./constants.ts"; declare global { interface Window { @@ -47,6 +47,7 @@ export interface NodeColorOptions { hasWarnings?: boolean; theme?: string; isFriendly?: boolean; + isHighlighted?: boolean; } export function getNodeColor( @@ -56,23 +57,44 @@ export function getNodeColor( id, hasWarnings = false, theme = "LIGHT", - isFriendly = false + isFriendly = false, + isHighlighted = false } = options; - const palette = CONSTANTS.COLORS[theme as keyof typeof CONSTANTS.COLORS]; + const palette = COLORS[theme as keyof typeof COLORS]; + let nodeColor: Color | undefined; // id 0 is the root package (so by default he is highlighted as selected). if (id === 0) { return palette.SELECTED; } else if (hasWarnings) { - return palette.WARN; + nodeColor = palette.WARN; } else if (isFriendly) { - return palette.FRIENDLY; + nodeColor = palette.FRIENDLY; + } + else { + nodeColor = palette.DEFAULT; + } + + if (isHighlighted) { + const borderColor = palette.HIGHLIGHTED.border; + + return { + color: { + background: nodeColor.color, + border: borderColor + }, + font: nodeColor.font ?? palette.DEFAULT.font, + borderWidth: 3, + shapeProperties: { + borderDashes: [6, 3] + } + }; } - return palette.DEFAULT; + return nodeColor; } export function getFlagsEmojisInlined( diff --git a/workspaces/vis-network/test/dataset.test.ts b/workspaces/vis-network/test/dataset.test.ts index 991dfb27..8c8dbc11 100644 --- a/workspaces/vis-network/test/dataset.test.ts +++ b/workspaces/vis-network/test/dataset.test.ts @@ -60,18 +60,50 @@ test("NodeSecureDataSet.prettySize", () => { test("NodeSecureDataSet.isHighlighted", async() => { const nsDataSet = new NodeSecureDataSet(); await nsDataSet.init(dataSetPayload); - assert.equal(nsDataSet.isHighlighted({ name: "Unknown" }), false, "should not be hightlighted"); - assert.equal(nsDataSet.isHighlighted({ name: "Sindre Sorhus" }), true, "name: Sindre Sorhus should be hightlighted"); - assert.equal(nsDataSet.isHighlighted({ name: "Rich Harris" }), true, "name: Rich Harris should be hightlighted"); - assert.equal(nsDataSet.isHighlighted({ email: "rich.harris@gmail.com" }), + assert.equal(nsDataSet.isHighlightedContact({ name: "Unknown" }), false, "should not be hightlighted"); + assert.equal(nsDataSet.isHighlightedContact({ name: "Sindre Sorhus" }), true, "name: Sindre Sorhus should be hightlighted"); + assert.equal(nsDataSet.isHighlightedContact({ name: "Rich Harris" }), true, "name: Rich Harris should be hightlighted"); + assert.equal(nsDataSet.isHighlightedContact({ email: "rich.harris@gmail.com" }), true, "email: rich.harris@gmail.com should be hightlighted"); - assert.equal(nsDataSet.isHighlighted({ email: "gentilhomme.thomas@gmail.com" }), + assert.equal(nsDataSet.isHighlightedContact({ email: "gentilhomme.thomas@gmail.com" }), true, "email: gentilhomme.thomas@gmail.com should be hightlighted"); }); +test("NodeSecureDataSet.init should mark highlighted packages by name", async() => { + const nsDataSet = new NodeSecureDataSet(); + await nsDataSet.init(dataSetPayload); + + const pkg3Packages = nsDataSet.findPackagesByName("pkg3"); + assert.ok(pkg3Packages.length > 0, "should have pkg3 packages"); + assert.ok(pkg3Packages.every((pkg) => pkg.isHighlighted), "all pkg3 versions should be highlighted (matched by name)"); +}); + +test("NodeSecureDataSet.init should mark highlighted packages by name@version", async() => { + const nsDataSet = new NodeSecureDataSet(); + await nsDataSet.init(dataSetPayload); + + const pkg2Packages = nsDataSet.findPackagesByName("pkg2"); + const highlighted = pkg2Packages.find((pkg) => pkg.version === "1.0.4"); + const notHighlighted = pkg2Packages.find((pkg) => pkg.version === "1.0.3"); + + assert.ok(highlighted, "should find pkg2@1.0.4"); + assert.ok(notHighlighted, "should find pkg2@1.0.3"); + assert.equal(highlighted.isHighlighted, true, "pkg2@1.0.4 should be highlighted (matched by name@version)"); + assert.equal(notHighlighted.isHighlighted, false, "pkg2@1.0.3 should not be highlighted"); +}); + +test("NodeSecureDataSet.init should not highlight packages absent from highlighted list", async() => { + const nsDataSet = new NodeSecureDataSet(); + await nsDataSet.init(dataSetPayload); + + const pkg1Packages = nsDataSet.findPackagesByName("pkg1"); + assert.ok(pkg1Packages.length > 0, "should have pkg1 packages"); + assert.ok(pkg1Packages.every((pkg) => !pkg.isHighlighted), "pkg1 should not be highlighted"); +}); + test("NodeSecureDataSet.computeAuthors", () => { const nsDataSet = new NodeSecureDataSet(); nsDataSet.computeAuthor({ name: "John Doe" }, "pkg@1.1"); @@ -156,7 +188,8 @@ test("NodeSecureDataSet.findPackagesByName should have packages when name matche hasWarnings: false, flags: "", links: undefined, - isFriendly: 0 + isFriendly: 0, + isHighlighted: false }, { id: undefined, @@ -165,7 +198,9 @@ test("NodeSecureDataSet.findPackagesByName should have packages when name matche hasWarnings: false, flags: "", links: undefined, - isFriendly: 0 + isFriendly: 0, + isHighlighted: true + } ]; diff --git a/workspaces/vis-network/test/fixtures/dataset-payload.json b/workspaces/vis-network/test/fixtures/dataset-payload.json index ccd1add6..2601137f 100644 --- a/workspaces/vis-network/test/fixtures/dataset-payload.json +++ b/workspaces/vis-network/test/fixtures/dataset-payload.json @@ -34,6 +34,10 @@ "string-width" ] } + ], + "packages": [ + "pkg3", + "pkg2@1.0.4" ] }, "dependencies": {