From 49a0641daeffa54cc24e48e07b224713fcd947db Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Thu, 23 Feb 2023 20:52:09 +0100 Subject: [PATCH 1/6] fix: fix and improve types --- .editorconfig | 9 + .../{nodejs.yml.disabled => nodejs.yml} | 0 package-lock.json | 20 - package.json | 17 +- scripts/build | 2 - scripts/githooks/pre-commit | 2 +- scripts/update_dist | 6 + src/helpers/helpers.ts | 80 +--- src/helpers/parse_claim.ts | 33 +- src/helpers/rank.ts | 26 +- src/helpers/simplify_claims.ts | 4 +- src/helpers/simplify_entity.ts | 57 ++- src/helpers/simplify_forms.ts | 2 +- src/helpers/simplify_sitelinks.ts | 43 +-- src/helpers/simplify_sparql_results.ts | 33 +- src/helpers/simplify_text_attributes.ts | 5 +- src/helpers/sitelinks.ts | 38 +- src/helpers/wikibase_time.ts | 132 +++++++ src/helpers/wikibase_time_to_date_object.ts | 41 -- src/index.ts | 9 +- src/queries/cirrus_search.ts | 7 +- src/queries/get_reverse_claims.ts | 33 +- src/types/claim.ts | 2 +- src/types/entity.ts | 24 +- src/types/lexeme.ts | 8 +- src/types/simplify_claims.ts | 4 +- src/types/sitelinks.ts | 2 +- src/types/sparql.ts | 13 +- src/types/terms.ts | 2 +- src/utils/utils.ts | 11 + src/wikibase-sdk.ts | 4 +- tests/cirrus_search.ts | 51 +-- tests/general.ts | 162 ++++---- tests/get_entities.ts | 33 +- tests/get_entities_from_sitelinks.ts | 26 +- tests/get_entity_revision.ts | 22 +- tests/get_many_entities.ts | 23 +- tests/get_reverse_claims.ts | 22 +- tests/get_revisions.ts | 48 +-- tests/helpers.ts | 351 +++++------------ tests/parse.ts | 20 +- tests/rank.ts | 17 +- tests/search_entities.ts | 36 +- tests/simplify_claims.ts | 354 +++++++++--------- tests/simplify_entity.ts | 119 +++--- tests/simplify_forms.ts | 30 +- tests/simplify_qualifiers.ts | 64 ++-- tests/simplify_references.ts | 8 +- tests/simplify_senses.ts | 26 +- tests/simplify_sitelinks.ts | 37 +- tests/simplify_sparql_results.ts | 98 ++--- tests/simplify_text_attributes.ts | 30 +- tests/sitelinks_helpers.ts | 179 ++++----- tests/sparql_query.ts | 8 +- tests/time.ts | 119 ++++++ tests/utils.ts | 18 +- tsconfig.json | 12 +- 57 files changed, 1288 insertions(+), 1294 deletions(-) create mode 100644 .editorconfig rename .github/workflows/{nodejs.yml.disabled => nodejs.yml} (100%) delete mode 100755 scripts/build create mode 100644 src/helpers/wikibase_time.ts delete mode 100644 src/helpers/wikibase_time_to_date_object.ts create mode 100644 tests/time.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..0f178672 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.github/workflows/nodejs.yml.disabled b/.github/workflows/nodejs.yml similarity index 100% rename from .github/workflows/nodejs.yml.disabled rename to .github/workflows/nodejs.yml diff --git a/package-lock.json b/package-lock.json index ea1dcca7..41a18c09 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,6 @@ "@types/lodash-es": "^4.17.6", "@types/mocha": "^10.0.1", "@types/node": "^18.11.18", - "@types/should": "^13.0.0", "@typescript-eslint/eslint-plugin": "^5.49.0", "@typescript-eslint/parser": "^5.49.0", "@vercel/git-hooks": "^1.0.0", @@ -314,16 +313,6 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, - "node_modules/@types/should": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/should/-/should-13.0.0.tgz", - "integrity": "sha512-Mi6YZ2ABnnGGFMuiBDP0a8s1ZDCDNHqP97UH8TyDmCWuGGavpsFMfJnAMYaaqmDlSCOCNbVLHBrSDEOpx/oLhw==", - "deprecated": "This is a stub types definition for should.js (https://github.com/shouldjs/should.js). should.js provides its own type definitions, so you don't need @types/should installed!", - "dev": true, - "dependencies": { - "should": "*" - } - }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz", @@ -4067,15 +4056,6 @@ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==", "dev": true }, - "@types/should": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@types/should/-/should-13.0.0.tgz", - "integrity": "sha512-Mi6YZ2ABnnGGFMuiBDP0a8s1ZDCDNHqP97UH8TyDmCWuGGavpsFMfJnAMYaaqmDlSCOCNbVLHBrSDEOpx/oLhw==", - "dev": true, - "requires": { - "should": "*" - } - }, "@typescript-eslint/eslint-plugin": { "version": "5.50.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.50.0.tgz", diff --git a/package.json b/package.json index 09ea9355..a25582c7 100644 --- a/package.json +++ b/package.json @@ -3,18 +3,18 @@ "version": "9.0.5", "type": "module", "description": "utils functions to query a Wikibase instance and simplify its results", - "main": "./dist/index.js", + "main": "./dist/src/index.js", "exports": { ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js" + "types": "./dist/src/index.d.ts", + "import": "./dist/src/index.js" }, "./wikidata.org": { - "types": "./dist/wellknown/wikidata.org.d.ts", - "import": "./dist/wellknown/wikidata.org.js" + "types": "./dist/src/wellknown/wikidata.org.d.ts", + "import": "./dist/src/wellknown/wikidata.org.js" } }, - "types": "./dist/index.d.ts", + "types": "./dist/src/index.d.ts", "files": [ "src", "dist" @@ -26,13 +26,13 @@ }, "scripts": { "add-fixture-entity": "./scripts/add_fixture_entity", - "build": "./scripts/build", + "build": "tsc", "check-supported-datatypes": "./scripts/check_supported_datatypes", "git-pre-commit": "./scripts/githooks/pre-commit", "lint": "eslint -c .eslintrc.cjs src scripts tests", "lint-fix": "npm run lint -- --fix", "test": "./scripts/run_tests", - "prepack": "npm run lint && npm test && npm run update-dist", + "prepack": "npm run lint && npm test", "postpublish": "./scripts/postpublish", "update-dist": "./scripts/update_dist", "update-sitelinks-languages": "./scripts/sitelinks_languages/update_sitelinks_languages", @@ -62,7 +62,6 @@ "@types/lodash-es": "^4.17.6", "@types/mocha": "^10.0.1", "@types/node": "^18.11.18", - "@types/should": "^13.0.0", "@typescript-eslint/eslint-plugin": "^5.49.0", "@typescript-eslint/parser": "^5.49.0", "@vercel/git-hooks": "^1.0.0", diff --git a/scripts/build b/scripts/build deleted file mode 100755 index 0a247517..00000000 --- a/scripts/build +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -tsc diff --git a/scripts/githooks/pre-commit b/scripts/githooks/pre-commit index e00664c3..ab649742 100755 --- a/scripts/githooks/pre-commit +++ b/scripts/githooks/pre-commit @@ -1,2 +1,2 @@ #!/usr/bin/env bash -npm run lint && npm run test +npm run build && npm run lint && npm run test diff --git a/scripts/update_dist b/scripts/update_dist index ab6da303..19e5e89d 100755 --- a/scripts/update_dist +++ b/scripts/update_dist @@ -5,6 +5,12 @@ green_log(){ echo -e "\e[0;32m$1\e[0;0m" ; } git checkout main +green_log 'ensure lints...' +npm run lint + +green_log 'ensure tests...' +npm test + green_log 'update dist...' green_log 'build...' diff --git a/src/helpers/helpers.ts b/src/helpers/helpers.ts index f1d04530..870e714e 100644 --- a/src/helpers/helpers.ts +++ b/src/helpers/helpers.ts @@ -1,4 +1,3 @@ -import { wikibaseTimeToDateObject as toDateObject } from './wikibase_time_to_date_object.js' import type { EntityId, EntityPageTitle, @@ -23,7 +22,7 @@ function isIdBuilder (regex: { readonly source: string, readon } export const isNumericId = isIdBuilder(/^[1-9][0-9]*$/) -export const isEntityId = isIdBuilder(/^((Q|P|L|M)[1-9][0-9]*|L[1-9][0-9]*-(F|S)[1-9][0-9]*)$/) +export const isEntityId = isIdBuilder(/^((Q|P|L|M|E)[1-9][0-9]*|L[1-9][0-9]*-(F|S)[1-9][0-9]*)$/) export const isEntitySchemaId = isIdBuilder(/^E[1-9][0-9]*$/) export const isItemId = isIdBuilder(/^Q[1-9][0-9]*$/) export const isPropertyId = isIdBuilder(/^P[1-9][0-9]*$/) @@ -31,10 +30,10 @@ export const isLexemeId = isIdBuilder(/^L[1-9][0-9]*$/) export const isFormId = isIdBuilder(/^L[1-9][0-9]*-F[1-9][0-9]*$/) export const isSenseId = isIdBuilder(/^L[1-9][0-9]*-S[1-9][0-9]*$/) export const isMediaInfoId = isIdBuilder(/^M[1-9][0-9]*$/) -export const isGuid = isIdBuilder(/^((Q|P|L|M)[1-9][0-9]*|L[1-9][0-9]*-(F|S)[1-9][0-9]*)\$[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i) +export const isGuid = isIdBuilder(/^((Q|P|L|M|E)[1-9][0-9]*|L[1-9][0-9]*-(F|S)[1-9][0-9]*)\$[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i) export const isHash = isIdBuilder(/^[0-9a-f]{40}$/) export const isRevisionId = isIdBuilder(/^\d+$/) -export const isNonNestedEntityId = isIdBuilder(/^(Q|P|L|M)[1-9][0-9]*$/) +export const isNonNestedEntityId = isIdBuilder(/^(Q|P|L|M|E)[1-9][0-9]*$/) export function isPropertyClaimsId (id: string): id is PropertyClaimsId { if (typeof id !== 'string') return false @@ -62,80 +61,9 @@ export function isEntityPageTitle (title: string): title is EntityPageTitle { export function getNumericId (id: string): NumericId { if (!isNonNestedEntityId(id)) throw new Error(`invalid entity id: ${id}`) - return id.replace(/^(Q|P|L|M)/, '') as NumericId + return id.replace(/^(Q|P|L|M|E)/, '') as NumericId } -export interface WikibaseTimeObject { - time: string - precision: number -} - -export type TimeInputValue = string | WikibaseTimeObject - -type TimeFunction = (wikibaseTime: TimeInputValue) => T - -// Try to parse the date or return the input -function bestEffort (fn: TimeFunction) { - return (value: TimeInputValue) => { - try { - return fn(value) - } catch { - value = typeof value === 'string' ? value : value.time - - const sign = value[0] - let [ yearMonthDay, withinDay ] = value.slice(1).split('T') - if (!sign || !yearMonthDay || !withinDay) { - throw new Error('TimeInput is invalid: ' + JSON.stringify(value)) - } - - yearMonthDay = yearMonthDay.replace(/-00/g, '-01') - - return `${sign}${yearMonthDay}T${withinDay}` - } - } -} - -const toEpochTime = (wikibaseTime: TimeInputValue) => toDateObject(wikibaseTime).getTime() -const toISOString = (wikibaseTime: TimeInputValue) => toDateObject(wikibaseTime).toISOString() - -// A date format that knows just three precisions: -// 'yyyy', 'yyyy-mm', and 'yyyy-mm-dd' (including negative and non-4 digit years) -// Should be able to handle the old and the new Wikidata time: -// - in the old one, units below the precision where set to 00 -// - in the new one, those months and days are set to 01 in those cases, -// so when we can access the full claim object, we check the precision -// to recover the old format -const toSimpleDay = (wikibaseTime: TimeInputValue): string => { - // Also accept claim datavalue.value objects, and actually prefer those, - // as we can check the precision - if (typeof wikibaseTime === 'object') { - const { time, precision } = wikibaseTime - // Year precision - if (precision === 9) wikibaseTime = time.replace('-01-01T', '-00-00T') - // Month precision - else if (precision === 10) wikibaseTime = time.replace('-01T', '-00T') - else wikibaseTime = time - } - - return wikibaseTime.split('T')[0] - // Remove positive years sign - .replace(/^\+/, '') - // Remove years padding zeros - .replace(/^(-?)0+/, '$1') - // Remove days if not included in the Wikidata date precision - .replace(/-00$/, '') - // Remove months if not included in the Wikidata date precision - .replace(/-00$/, '') -} - -export const wikibaseTimeToEpochTime = bestEffort(toEpochTime) - -export const wikibaseTimeToISOString = bestEffort(toISOString) - -export const wikibaseTimeToSimpleDay = bestEffort(toSimpleDay) - -export const wikibaseTimeToDateObject = toDateObject - export function getImageUrl (filename: string, width?: number): Url { let url = `https://commons.wikimedia.org/wiki/Special:FilePath/${filename}` if (typeof width === 'number') url += `?width=${width}` diff --git a/src/helpers/parse_claim.ts b/src/helpers/parse_claim.ts index a20afd01..2794ed64 100644 --- a/src/helpers/parse_claim.ts +++ b/src/helpers/parse_claim.ts @@ -1,5 +1,5 @@ -import { wikibaseTimeToISOString, wikibaseTimeToEpochTime, wikibaseTimeToSimpleDay } from './helpers.js' -import type { TimeInputValue } from './helpers.js' +import { convertTime } from './wikibase_time.js' +import type { SimplifySnakOptions } from '../types/simplify_claims.js' const simple = datavalue => datavalue.value @@ -46,13 +46,8 @@ const coordinate = (datavalue, options) => { } } -const time = (datavalue, options) => { - let timeValue - if (typeof options.timeConverter === 'function') { - timeValue = options.timeConverter(datavalue.value) - } else { - timeValue = getTimeConverter(options.timeConverter)(datavalue.value) - } +const time = (datavalue, options: SimplifySnakOptions) => { + const timeValue = convertTime(options.timeConverter, datavalue.value) if (options.keepRichValues) { const { timezone, before, after, precision, calendarmodel } = datavalue.value return { time: timeValue, timezone, before, after, precision, calendarmodel } @@ -61,22 +56,6 @@ const time = (datavalue, options) => { } } -const getTimeConverter = (key = 'iso') => { - const converter = timeConverters[key] - if (!converter) throw new Error(`invalid converter key: ${JSON.stringify(key).substring(0, 100)}`) - return converter -} - -// Each time converter should be able to accept 2 keys of arguments: -// - either datavalue.value objects (prefered as it gives access to the precision) -// - or the time string (datavalue.value.time) -export const timeConverters = { - iso: wikibaseTimeToISOString, - epoch: wikibaseTimeToEpochTime, - 'simple-day': wikibaseTimeToSimpleDay, - none: (wikibaseTime: TimeInputValue) => typeof wikibaseTime === 'string' ? wikibaseTime : wikibaseTime.time, -} as const - export const parsers = { commonsMedia: simple, 'external-id': simple, @@ -106,8 +85,8 @@ export function parseClaim (datatype, datavalue, options, claimId) { try { return parsers[datatype](datavalue, options) - } catch (err) { - if (err.message === 'parsers[datatype] is not a function') { + } catch (err: unknown) { + if (err instanceof Error && err.message === 'parsers[datatype] is not a function') { err.message = `${datatype} claim parser isn't implemented Claim id: ${claimId} Please report to https://github.com/maxlath/wikibase-sdk/issues` diff --git a/src/helpers/rank.ts b/src/helpers/rank.ts index 0e1e6e3c..91799352 100644 --- a/src/helpers/rank.ts +++ b/src/helpers/rank.ts @@ -1,7 +1,14 @@ -import type { Claims, PropertyClaims } from '../types/claim.js' +import { typedEntries } from '../utils/utils.js' +import type { Claim, Claims, PropertyClaims, Rank } from '../types/claim.js' export function truthyPropertyClaims (propertyClaims: PropertyClaims): PropertyClaims { - const aggregate = propertyClaims.reduce(aggregatePerRank, {}) + const aggregate: Partial> = {} + for (const claim of propertyClaims) { + const { rank } = claim + aggregate[rank] ??= [] + aggregate[rank].push(claim) + } + // on truthyness: https://www.mediawiki.org/wiki/Wikibase/Indexing/RDF_Dump_Format#Truthy_statements return aggregate.preferred || aggregate.normal || [] } @@ -10,17 +17,10 @@ export function nonDeprecatedPropertyClaims (propertyClaims: PropertyClaims): Pr return propertyClaims.filter(claim => claim.rank !== 'deprecated') } -const aggregatePerRank = (aggregate, claim) => { - const { rank } = claim - aggregate[rank] || (aggregate[rank] = []) - aggregate[rank].push(claim) - return aggregate -} - export function truthyClaims (claims: Claims): Claims { - const truthClaimsOnly = {} - Object.keys(claims).forEach(property => { - truthClaimsOnly[property] = truthyPropertyClaims(claims[property]) - }) + const truthClaimsOnly: Claims = {} + for (const [ property, value ] of typedEntries(claims)) { + truthClaimsOnly[property] = truthyPropertyClaims(value) + } return truthClaimsOnly } diff --git a/src/helpers/simplify_claims.ts b/src/helpers/simplify_claims.ts index 6b3e25a7..69c795f0 100644 --- a/src/helpers/simplify_claims.ts +++ b/src/helpers/simplify_claims.ts @@ -1,7 +1,7 @@ import { uniq } from '../utils/utils.js' import { parseClaim } from './parse_claim.js' import { truthyPropertyClaims, nonDeprecatedPropertyClaims } from './rank.js' -import type { Claim, Claims, PropertyClaims, PropertyQualifiers, Qualifier, Qualifiers } from '../types/claim.js' +import type { Claim, Claims, PropertyClaims, PropertyQualifiers, Qualifier, Qualifiers, Reference } from '../types/claim.js' import type { SimplifiedClaim, SimplifiedClaims, SimplifiedPropertyClaims, SimplifySnakOptions, SimplifySnaksOptions } from '../types/simplify_claims.js' function simplifySnaks (snaks, options) { @@ -148,7 +148,7 @@ export function simplifyQualifier (qualifier: Qualifier, options: SimplifySnakOp return simplifySnak(qualifier, options) } -export function simplifyReferences (references, options) { +export function simplifyReferences (references: Reference[], options) { return references.map(refRecord => simplifyReferenceRecord(refRecord, options)) } export function simplifyReferenceRecord (refRecord, options) { diff --git a/src/helpers/simplify_entity.ts b/src/helpers/simplify_entity.ts index 91912dae..67ded80d 100644 --- a/src/helpers/simplify_entity.ts +++ b/src/helpers/simplify_entity.ts @@ -1,43 +1,40 @@ import * as simplify from './simplify.js' -import type { Entities, Entity, SimplifiedEntity } from '../types/entity.js' +import type { Entities, Entity, SimplifiedEntity, SimplifiedItem, SimplifiedProperty, SimplifiedLexeme } from '../types/entity.js' import type { SimplifyEntityOptions } from '../types/options.js' export const simplifyEntity = (entity: Entity, options: SimplifyEntityOptions = {}): SimplifiedEntity => { - const { type } = entity - const simplified: any = { - id: entity.id, - type, - modified: entity.modified, - } - + const { id, modified, type } = entity if (type === 'item') { - simplifyIfDefined(entity, simplified, 'labels') - simplifyIfDefined(entity, simplified, 'descriptions') - simplifyIfDefined(entity, simplified, 'aliases') - simplifyIfDefined(entity, simplified, 'claims', options) - simplifyIfDefined(entity, simplified, 'sitelinks', options) + const simplified: SimplifiedItem = { id, type, modified } + + if (entity.labels != null) simplified.labels = simplify.labels(entity.labels) + if (entity.descriptions != null) simplified.descriptions = simplify.descriptions(entity.descriptions) + if (entity.aliases != null) simplified.aliases = simplify.aliases(entity.aliases) + if (entity.claims != null) simplified.claims = simplify.claims(entity.claims, options) + if (entity.sitelinks != null) simplified.sitelinks = simplify.sitelinks(entity.sitelinks, options) + + return simplified } else if (type === 'property') { - simplified.datatype = entity.datatype - simplifyIfDefined(entity, simplified, 'labels') - simplifyIfDefined(entity, simplified, 'descriptions') - simplifyIfDefined(entity, simplified, 'aliases') - simplifyIfDefined(entity, simplified, 'claims', options) + const simplified: SimplifiedProperty = { id, type, modified, datatype: entity.datatype } + + if (entity.labels != null) simplified.labels = simplify.labels(entity.labels) + if (entity.descriptions != null) simplified.descriptions = simplify.descriptions(entity.descriptions) + if (entity.aliases != null) simplified.aliases = simplify.aliases(entity.aliases) + if (entity.claims != null) simplified.claims = simplify.claims(entity.claims, options) + + return simplified } else if (type === 'lexeme') { - simplifyIfDefined(entity, simplified, 'lemmas') - simplified.lexicalCategory = entity.lexicalCategory - simplified.language = entity.language - simplifyIfDefined(entity, simplified, 'claims', options) - simplifyIfDefined(entity, simplified, 'forms', options) - simplifyIfDefined(entity, simplified, 'senses', options) - } + const simplified: SimplifiedLexeme = { id, type, modified, lexicalCategory: entity.lexicalCategory, language: entity.language } - return simplified -} + if (entity.lemmas != null) simplified.lemmas = simplify.lemmas(entity.lemmas) + if (entity.claims != null) simplified.claims = simplify.claims(entity.claims, options) + if (entity.forms != null) simplified.forms = simplify.forms(entity.forms, options) + if (entity.senses != null) simplified.senses = simplify.senses(entity.senses, options) -const simplifyIfDefined = (entity, simplified, attribute, options?) => { - if (entity[attribute] != null) { - simplified[attribute] = simplify[attribute](entity[attribute], options) + return simplified } + + return { id, type, modified } } export const simplifyEntities = (entities: Entities, options: SimplifyEntityOptions = {}) => { diff --git a/src/helpers/simplify_forms.ts b/src/helpers/simplify_forms.ts index 567a4dc3..8048fcc1 100644 --- a/src/helpers/simplify_forms.ts +++ b/src/helpers/simplify_forms.ts @@ -15,4 +15,4 @@ export const simplifyForm = (form: Form, options: SimplifyClaimsOptions = {}): S } } -export const simplifyForms = (forms, options) => forms.map(form => simplifyForm(form, options)) +export const simplifyForms = (forms: readonly Form[], options: SimplifyClaimsOptions = {}) => forms.map(form => simplifyForm(form, options)) diff --git a/src/helpers/simplify_sitelinks.ts b/src/helpers/simplify_sitelinks.ts index e79e57c8..0297f37b 100644 --- a/src/helpers/simplify_sitelinks.ts +++ b/src/helpers/simplify_sitelinks.ts @@ -1,3 +1,4 @@ +import { typedEntries } from '../utils/utils.js' import { getSitelinkUrl } from './sitelinks.js' import type { SimplifySitelinkOptions } from '../types/options.js' import type { SimplifiedSitelinks, Sitelinks } from '../types/sitelinks.js' @@ -5,28 +6,28 @@ import type { SimplifiedSitelinks, Sitelinks } from '../types/sitelinks.js' export function simplifySitelinks (sitelinks: Sitelinks, options: SimplifySitelinkOptions = {}): SimplifiedSitelinks { let { addUrl, keepBadges, keepAll } = options keepBadges = keepBadges || keepAll - return Object.keys(sitelinks).reduce(aggregateValues({ - sitelinks, - addUrl, - keepBadges, - }), {}) -} -const aggregateValues = ({ sitelinks, addUrl, keepBadges }) => (index, key) => { - // Accomodating for wikibase-cli, which might set the sitelink to null - // to signify that a requested sitelink was not found - if (sitelinks[key] == null) { - index[key] = sitelinks[key] - return index - } + const result: SimplifiedSitelinks = {} - const { title, badges } = sitelinks[key] - if (addUrl || keepBadges) { - index[key] = { title } - if (addUrl) index[key].url = getSitelinkUrl({ site: key, title }) - if (keepBadges) index[key].badges = badges - } else { - index[key] = title + for (const [ key, value ] of typedEntries(sitelinks)) { + // Accomodating for wikibase-cli, which might set the sitelink to null + // to signify that a requested sitelink was not found + if (value == null) { + result[key] = null + continue + } + + const { title, badges } = value + if (addUrl || keepBadges) { + result[key] = { title } + // @ts-expect-error TypeScript cant assume which of the two types it is + if (addUrl) result[key].url = getSitelinkUrl({ site: key, title }) + // @ts-expect-error TypeScript cant assume which of the two types it is + if (keepBadges) result[key].badges = badges + } else { + result[key] = title + } } - return index + + return result } diff --git a/src/helpers/simplify_sparql_results.ts b/src/helpers/simplify_sparql_results.ts index b9f65263..2cc2228a 100644 --- a/src/helpers/simplify_sparql_results.ts +++ b/src/helpers/simplify_sparql_results.ts @@ -1,9 +1,12 @@ -import type { SimplifiedSparqlResults, SparqlResults } from '../types/sparql.js' +import type { SimplifiedSparqlResultMinified, SimplifiedSparqlResultRecord, SimplifiedSparqlResults, SparqlResults, SparqlValueObj, SparqlValueRaw, SparqlValueType } from '../types/sparql.js' export type SimplifySparqlResultsOptions = { readonly minimize?: boolean } +export function simplifySparqlResults (input: SparqlResults): SimplifiedSparqlResultRecord +export function simplifySparqlResults (input: SparqlResults, options: { readonly minimize: true }): SimplifiedSparqlResultMinified + export function simplifySparqlResults (input: SparqlResults, options: SimplifySparqlResultsOptions = {}): SimplifiedSparqlResults { if (typeof input === 'string') { input = JSON.parse(input) @@ -12,7 +15,7 @@ export function simplifySparqlResults (input: SparqlResults, options: SimplifySp const { vars } = input.head const results = input.results.bindings - if (vars.length === 1 && options.minimize === true) { + if (vars.length === 1 && options.minimize) { const varName = vars[0] return results .map(result => parseValue(result[varName])) @@ -24,13 +27,7 @@ export function simplifySparqlResults (input: SparqlResults, options: SimplifySp return results.map(getSimplifiedResult(richVars, associatedVars, standaloneVars)) } -type ValueObj = { - readonly type: 'uri' | 'bnode' - readonly datatype?: string - readonly value: string -} - -function parseValue (valueObj: ValueObj | undefined): string | number | boolean | null { +function parseValue (valueObj: SparqlValueObj | undefined): string | number | boolean | null { // blank nodes will be filtered-out in order to get things simple if (!valueObj || valueObj.type === 'bnode') return null @@ -72,7 +69,7 @@ function convertStatementUriToGuid (uri: string) { return parts[0] + '$' + parts.slice(1).join('-') } -const identifyVars = vars => { +const identifyVars = (vars: readonly string[]) => { let richVars = vars.filter(varName => vars.some(isAssociatedVar(varName))) richVars = richVars.filter(richVar => { return !richVars.some(otherRichVar => { @@ -87,15 +84,15 @@ const identifyVars = vars => { return { richVars, associatedVars, standaloneVars } } -const isAssociatedVar = varNameA => { +const isAssociatedVar = (varNameA: string) => { const pattern = new RegExp(`^${varNameA}[A-Z]\\w+`) return pattern.test.bind(pattern) } -const getSimplifiedResult = (richVars, associatedVars, standaloneVars) => result => { - const simplifiedResult = {} +const getSimplifiedResult = (richVars: readonly string[], associatedVars: readonly string[], standaloneVars: readonly string[]) => (result: Record) => { + const simplifiedResult: Record = {} for (const varName of richVars) { - const richVarData: any = {} + const richVarData: Record = {} const value = parseValue(result[varName]) if (value != null) richVarData.value = value for (const associatedVarName of associatedVars) { @@ -109,17 +106,13 @@ const getSimplifiedResult = (richVars, associatedVars, standaloneVars) => result return simplifiedResult } -const addAssociatedValue = (result, varName, associatedVarName, richVarData) => { +const addAssociatedValue = (result: Record, varName: string, associatedVarName: string, richVarData: Record) => { // ex: propertyType => Type let shortAssociatedVarName = associatedVarName.split(varName)[1] // ex: Type => type shortAssociatedVarName = shortAssociatedVarName[0].toLowerCase() + shortAssociatedVarName.slice(1) // ex: altLabel => aliases - shortAssociatedVarName = specialNames[shortAssociatedVarName] || shortAssociatedVarName + shortAssociatedVarName = shortAssociatedVarName === 'altLabel' ? 'aliases' : shortAssociatedVarName const associatedVarData = result[associatedVarName] if (associatedVarData != null) richVarData[shortAssociatedVarName] = associatedVarData.value } - -const specialNames = { - altLabel: 'aliases', -} as const diff --git a/src/helpers/simplify_text_attributes.ts b/src/helpers/simplify_text_attributes.ts index 8f208ea3..bf670664 100644 --- a/src/helpers/simplify_text_attributes.ts +++ b/src/helpers/simplify_text_attributes.ts @@ -1,3 +1,4 @@ +import { typedEntries } from '../utils/utils.js' import type { WmLanguageCode } from '../types/options.js' import type { Aliases, Descriptions, Glosses, Labels, Lemmas, Representations, SimplifiedAliases, SimplifiedDescriptions, SimplifiedGlosses, SimplifiedLabels, SimplifiedLemmas, SimplifiedRepresentations } from '../types/terms.js' @@ -5,7 +6,7 @@ type InValue = { readonly value: T } function singleValue (data: Partial>>>) { const simplified: Partial> = {} - for (const [ lang, obj ] of Object.entries(data)) { + for (const [ lang, obj ] of typedEntries(data)) { simplified[lang] = obj != null ? obj.value : null } return simplified @@ -13,7 +14,7 @@ function singleValue (data: Partial (data: Partial>>>>) { const simplified: Partial> = {} - for (const [ lang, obj ] of Object.entries(data)) { + for (const [ lang, obj ] of typedEntries(data)) { simplified[lang] = obj != null ? obj.map(o => o.value) : [] } return simplified diff --git a/src/helpers/sitelinks.ts b/src/helpers/sitelinks.ts index 8d6a9c57..e19218f8 100644 --- a/src/helpers/sitelinks.ts +++ b/src/helpers/sitelinks.ts @@ -1,7 +1,6 @@ -import { fixedEncodeURIComponent, isOfType, rejectObsoleteInterface, replaceSpaceByUnderscores } from '../utils/utils.js' +import { fixedEncodeURIComponent, isOfType, isAKey, rejectObsoleteInterface, replaceSpaceByUnderscores } from '../utils/utils.js' import { languages } from './sitelinks_languages.js' import { specialSites } from './special_sites.js' -import type { EntityId } from '../types/entity.js' import type { Url, WmLanguageCode } from '../types/options.js' import type { Site } from '../types/sitelinks.js' @@ -18,9 +17,14 @@ export function getSitelinkUrl ({ site, title }: GetSitelinkUrlOptions): Url { if (!site) throw new Error('missing a site') if (!title) throw new Error('missing a title') + if (isAKey(siteUrlBuilders, site)) { + return siteUrlBuilders[site](title) + } + const shortSiteKey = site.replace(/wiki$/, '') - const specialUrlBuilder = siteUrlBuilders[shortSiteKey] || siteUrlBuilders[site] - if (specialUrlBuilder) return specialUrlBuilder(title) + if (isAKey(siteUrlBuilders, shortSiteKey)) { + return siteUrlBuilders[shortSiteKey](title) + } const { lang, project } = getSitelinkData(site) title = fixedEncodeURIComponent(replaceSpaceByUnderscores(title)) @@ -34,9 +38,8 @@ const siteUrlBuilders = { mediawiki: (title: string) => `https://www.mediawiki.org/wiki/${title}`, meta: wikimediaSite('meta'), species: wikimediaSite('species'), - wikidata: (entityId: EntityId) => { - const prefix = prefixByEntityLetter[entityId[0]] - let title = prefix ? `${prefix}:${entityId}` : entityId + wikidata: (entityId: string) => { + let title = prefixByEntity(entityId) // Required for forms and senses title = title.replace('-', '#') return `${wikidataBase}${title}` @@ -44,11 +47,12 @@ const siteUrlBuilders = { wikimania: wikimediaSite('wikimania'), } as const -const prefixByEntityLetter = { - E: 'EntitySchema', - L: 'Lexeme', - P: 'Property', -} as const +function prefixByEntity (entityId: string) { + if (entityId.startsWith('E')) return `EntitySchema:${entityId}` + if (entityId.startsWith('L')) return `Lexeme:${entityId}` + if (entityId.startsWith('P')) return `Property:${entityId}` + return entityId +} const sitelinkUrlPattern = /^https?:\/\/([\w-]{2,10})\.(\w+)\.org\/\w+\/(.*)/ @@ -84,9 +88,9 @@ export function getSitelinkData (site: Site | Url): SitelinkData { return { lang, project, key, title, url } } else { const key = site - const specialProjectName = specialSites[key] - if (specialProjectName) { - return { lang: 'en', project: specialProjectName, key } + if (isAKey(specialSites, site)) { + const project = specialSites[site] + return { lang: 'en', project, key } } let [ lang, projectSuffix, rest ] = key.split('wik') @@ -101,10 +105,10 @@ export function getSitelinkData (site: Site | Url): SitelinkData { // Support keys such as be_x_oldwiki, which refers to be-x-old.wikipedia.org lang = lang.replace(/_/g, '-') + if (!isAKey(projectsBySuffix, projectSuffix)) throw new Error(`sitelink project not found: ${site}`) const project = projectsBySuffix[projectSuffix] - if (!project) throw new Error(`sitelink project not found: ${project}`) - // @ts-expect-error + // @ts-expect-error lang has replaced _ with - and is not a perfect WmLanguageCode anymore return { lang, project, key } } } diff --git a/src/helpers/wikibase_time.ts b/src/helpers/wikibase_time.ts new file mode 100644 index 00000000..ee927f78 --- /dev/null +++ b/src/helpers/wikibase_time.ts @@ -0,0 +1,132 @@ +import { isAKey } from '../utils/utils.js' + +export function wikibaseTimeToDateObject (wikibaseTime: TimeInputValue): Date { + // Also accept claim datavalue.value objects + if (typeof wikibaseTime === 'object') { + wikibaseTime = wikibaseTime.time + } + + const sign = wikibaseTime[0] + let [ yearMonthDay, withinDay ] = wikibaseTime.slice(1).split('T') + + // Wikidata generates invalid ISO dates to indicate precision + // ex: +1990-00-00T00:00:00Z to indicate 1990 with year precision + yearMonthDay = yearMonthDay.replace(/-00/g, '-01') + const rest = `${yearMonthDay}T${withinDay}` + + return fullDateData(sign, rest) +} + +const fullDateData = (sign: string, rest: string) => { + const year = rest.split('-')[0] + const needsExpandedYear = sign === '-' || year.length > 4 + + return needsExpandedYear ? expandedYearDate(sign, rest, year) : new Date(rest) +} + +const expandedYearDate = (sign: string, rest: string, year: string) => { + let date: string + // Using ISO8601 expanded notation for negative years or positive + // years with more than 4 digits: adding up to 2 leading zeros + // when needed. Can't find the documentation again, but testing + // with `new Date(date)` gives a good clue of the implementation + if (year.length === 4) { + date = `${sign}00${rest}` + } else if (year.length === 5) { + date = `${sign}0${rest}` + } else { + date = sign + rest + } + return new Date(date) +} + +export interface WikibaseTimeObject { + time: string + precision: number +} + +export type TimeInputValue = string | WikibaseTimeObject + +type TimeFunction = (wikibaseTime: TimeInputValue) => T + +// Try to parse the date or return the input +function bestEffort (fn: TimeFunction) { + return (value: TimeInputValue) => { + try { + return fn(value) + } catch { + value = typeof value === 'string' ? value : value.time + + const sign = value[0] + let [ yearMonthDay, withinDay ] = value.slice(1).split('T') + if (!sign || !yearMonthDay || !withinDay) { + throw new Error('TimeInput is invalid: ' + JSON.stringify(value)) + } + + yearMonthDay = yearMonthDay.replace(/-00/g, '-01') + + return `${sign}${yearMonthDay}T${withinDay}` + } + } +} + +const toEpochTime = (wikibaseTime: TimeInputValue) => wikibaseTimeToDateObject(wikibaseTime).getTime() +const toISOString = (wikibaseTime: TimeInputValue) => wikibaseTimeToDateObject(wikibaseTime).toISOString() + +// A date format that knows just three precisions: +// 'yyyy', 'yyyy-mm', and 'yyyy-mm-dd' (including negative and non-4 digit years) +// Should be able to handle the old and the new Wikidata time: +// - in the old one, units below the precision where set to 00 +// - in the new one, those months and days are set to 01 in those cases, +// so when we can access the full claim object, we check the precision +// to recover the old format +const toSimpleDay = (wikibaseTime: TimeInputValue): string => { + // Also accept claim datavalue.value objects, and actually prefer those, + // as we can check the precision + if (typeof wikibaseTime === 'object') { + const { time, precision } = wikibaseTime + // Year precision + if (precision === 9) wikibaseTime = time.replace('-01-01T', '-00-00T') + // Month precision + else if (precision === 10) wikibaseTime = time.replace('-01T', '-00T') + else wikibaseTime = time + } + + return wikibaseTime.split('T')[0] + // Remove positive years sign + .replace(/^\+/, '') + // Remove years padding zeros + .replace(/^(-?)0+/, '$1') + // Remove days if not included in the Wikidata date precision + .replace(/-00$/, '') + // Remove months if not included in the Wikidata date precision + .replace(/-00$/, '') +} + +export const wikibaseTimeToEpochTime = bestEffort(toEpochTime) +export const wikibaseTimeToISOString = bestEffort(toISOString) +export const wikibaseTimeToSimpleDay = bestEffort(toSimpleDay) + +export function convertTime (timeConverter: TimeConverter | TimeFunction = 'iso', wikibaseTime: TimeInputValue) { + if (typeof timeConverter === 'function') { + return timeConverter(wikibaseTime) + } + + if (isAKey(timeConverters, timeConverter)) { + return timeConverters[timeConverter](wikibaseTime) + } + + throw new Error(`invalid time converter key: ${JSON.stringify(timeConverter).substring(0, 100)}`) +} + +// Each time converter should be able to accept 2 keys of arguments: +// - either datavalue.value objects (prefered as it gives access to the precision) +// - or the time string (datavalue.value.time) +const timeConverters = { + iso: wikibaseTimeToISOString, + epoch: wikibaseTimeToEpochTime, + 'simple-day': wikibaseTimeToSimpleDay, + none: (wikibaseTime: TimeInputValue) => typeof wikibaseTime === 'string' ? wikibaseTime : wikibaseTime.time, +} as const +export type TimeConverterFn = TimeFunction +export type TimeConverter = keyof typeof timeConverters diff --git a/src/helpers/wikibase_time_to_date_object.ts b/src/helpers/wikibase_time_to_date_object.ts deleted file mode 100644 index 87ebc66d..00000000 --- a/src/helpers/wikibase_time_to_date_object.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { TimeInputValue } from './helpers.js' - -export function wikibaseTimeToDateObject (wikibaseTime: TimeInputValue): Date { - // Also accept claim datavalue.value objects - if (typeof wikibaseTime === 'object') { - wikibaseTime = wikibaseTime.time - } - - const sign = wikibaseTime[0] - let [ yearMonthDay, withinDay ] = wikibaseTime.slice(1).split('T') - - // Wikidata generates invalid ISO dates to indicate precision - // ex: +1990-00-00T00:00:00Z to indicate 1990 with year precision - yearMonthDay = yearMonthDay.replace(/-00/g, '-01') - const rest = `${yearMonthDay}T${withinDay}` - - return fullDateData(sign, rest) -} - -const fullDateData = (sign: string, rest: string) => { - const year = rest.split('-')[0] - const needsExpandedYear = sign === '-' || year.length > 4 - - return needsExpandedYear ? expandedYearDate(sign, rest, year) : new Date(rest) -} - -const expandedYearDate = (sign: string, rest: string, year: string) => { - let date: string - // Using ISO8601 expanded notation for negative years or positive - // years with more than 4 digits: adding up to 2 leading zeros - // when needed. Can't find the documentation again, but testing - // with `new Date(date)` gives a good clue of the implementation - if (year.length === 4) { - date = `${sign}00${rest}` - } else if (year.length === 5) { - date = `${sign}0${rest}` - } else { - date = sign + rest - } - return new Date(date) -} diff --git a/src/index.ts b/src/index.ts index 765c7b6c..45572c61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,12 +2,10 @@ import { WBK } from './wikibase-sdk.js' export default WBK -export * from './wikibase-sdk.js' -export * from './helpers/helpers.js' -export * from './helpers/rank.js' -export * from './helpers/sitelinks.js' export * as parse from './helpers/parse_responses.js' export * as simplify from './helpers/simplify.js' +export * from './helpers/helpers.js' +export * from './helpers/rank.js' export * from './helpers/simplify_claims.js' export * from './helpers/simplify_entity.js' export * from './helpers/simplify_forms.js' @@ -15,6 +13,8 @@ export * from './helpers/simplify_senses.js' export * from './helpers/simplify_sitelinks.js' export * from './helpers/simplify_sparql_results.js' export * from './helpers/simplify_text_attributes.js' +export * from './helpers/sitelinks.js' +export * from './helpers/wikibase_time.js' export * from './types/claim.js' export * from './types/entity.js' export * from './types/lexeme.js' @@ -24,3 +24,4 @@ export * from './types/simplify_claims.js' export * from './types/sitelinks.js' export * from './types/sparql.js' export * from './types/terms.js' +export * from './wikibase-sdk.js' diff --git a/src/queries/cirrus_search.ts b/src/queries/cirrus_search.ts index b54c5f24..eb869899 100644 --- a/src/queries/cirrus_search.ts +++ b/src/queries/cirrus_search.ts @@ -1,6 +1,6 @@ // See https://www.wikidata.org/w/api.php?action=help&modules=query%2Bsearch -import { rejectObsoleteInterface } from '../utils/utils.js' +import { isAKey, rejectObsoleteInterface } from '../utils/utils.js' import type { Url, UrlResultFormat } from '../types/options.js' import type { BuildUrlFunction } from '../utils/build_url.js' @@ -23,11 +23,12 @@ export function cirrusSearchPagesFactory (buildUrl: BuildUrlFunction) { rejectObsoleteInterface(arguments) // Accept sr parameters with or without prefix - for (const key in options) { + for (const [ key, value ] of Object.entries(options)) { if (key.startsWith('sr')) { const shortKey = key.replace(/^sr/, '') + if (!isAKey(options, shortKey)) throw new Error(`${key} is not a valid option`) if (options[shortKey] != null) throw new Error(`${shortKey} and ${key} are the same`) - options[shortKey] = options[key] + options[shortKey] = value } } diff --git a/src/queries/get_reverse_claims.ts b/src/queries/get_reverse_claims.ts index 2c45726a..ff03c820 100644 --- a/src/queries/get_reverse_claims.ts +++ b/src/queries/get_reverse_claims.ts @@ -10,9 +10,12 @@ import type { Url } from '../types/options.js' // https://www.mediawiki.org/wiki/Wikibase/Indexing/RDF_Dump_Format#WDQS_data_differences const itemsOnly = 'FILTER NOT EXISTS { ?subject rdf:type wikibase:Property . } ' +type Value = string | number +type Values = Value | readonly Value[] + export interface GetReverseClaimsOptions { - properties: PropertyId | PropertyId[] - values: string | number | string[] | number[] + properties: PropertyId | readonly PropertyId[] + values: Values limit?: number caseInsensitive?: boolean keepProperties?: boolean @@ -37,40 +40,38 @@ export const getReverseClaimsFactory = (sparqlEndpoint: Url) => { } } -const getValueBlock = (values, valueFn, properties, filter) => { - properties = properties.map(prefixifyProperty).join('|') +const getValueBlock = (values: Values, valueFn: ValueFn, properties: readonly PropertyId[], filter: string) => { + const propertiesString = properties.map(property => 'wdt:' + property).join('|') if (!(values instanceof Array)) { - return valueFn(properties, getValueString(values), filter) + return valueFn(propertiesString, getValueString(values), filter) } const valuesBlocks = values .map(getValueString) - .map(valStr => valueFn(properties, valStr, filter)) + .map(valStr => valueFn(propertiesString, valStr, filter)) return '{ ' + valuesBlocks.join('} UNION {') + ' }' } -const getValueString = value => { - if (isItemId(value)) { - value = `wd:${value}` - } else if (typeof value === 'string') { - value = `'${value}'` +const getValueString = (value: Value) => { + if (typeof value === 'string') { + return isItemId(value) ? `wd:${value}` : `'${value}'` } - return value + return String(value) } -const directValueQuery = (properties, value, filter) => { +type ValueFn = (properties: string, value: string, filter: string) => string + +const directValueQuery: ValueFn = (properties, value, filter) => { return `?subject ${properties} ${value} . ${filter}` } // Discussion on how to make this query optimal: // http://stackoverflow.com/q/43073266/3324977 -const caseInsensitiveValueQuery = (properties, value, filter) => { +const caseInsensitiveValueQuery: ValueFn = (properties, value, filter) => { return `?subject ${properties} ?value . FILTER (lcase(?value) = ${value.toLowerCase()}) ${filter}` } - -const prefixifyProperty = property => 'wdt:' + property diff --git a/src/types/claim.ts b/src/types/claim.ts index 014b8a9f..dc6a3930 100644 --- a/src/types/claim.ts +++ b/src/types/claim.ts @@ -11,7 +11,7 @@ export interface Claim { rank: Rank type: DataType qualifiers?: Qualifiers - 'qualifiers-order'?: string[] + 'qualifiers-order'?: PropertyId[] references?: Reference[] } diff --git a/src/types/entity.ts b/src/types/entity.ts index 97d05f55..179c0f07 100644 --- a/src/types/entity.ts +++ b/src/types/entity.ts @@ -1,5 +1,5 @@ import type { Claims, DataType } from './claim.js' -import type { Forms, Senses, SimplifiedForms, SimplifiedSenses } from './lexeme.js' +import type { Form, Sense, SimplifiedForm, SimplifiedSense } from './lexeme.js' import type { SimplifiedClaims } from './simplify_claims.js' import type { SimplifiedSitelinks, Sitelinks } from './sitelinks.js' import type { Aliases, Descriptions, Labels, Lemmas, SimplifiedAliases, SimplifiedDescriptions, SimplifiedLabels, SimplifiedLemmas } from './terms.js' @@ -20,13 +20,13 @@ export type RevisionId = `${number}` export type PropertyClaimsId = `${EntityId}#${PropertyId}` export type EntityId = NonNestedEntityId | FormId | SenseId -export type NonNestedEntityId = ItemId | PropertyId | LexemeId | MediaInfoId -export type NamespacedEntityId = `Item:${ItemId}` | `Lexeme:${LexemeId}` | `Property:${PropertyId}` +export type NonNestedEntityId = ItemId | PropertyId | LexemeId | EntitySchemaId | MediaInfoId +export type NamespacedEntityId = `Item:${ItemId}` | `Lexeme:${LexemeId}` | `Property:${PropertyId}` | `EntitySchema:${EntitySchemaId}` export type Guid = string export type Hash = string -export type Entity = (Property | Item | Lexeme) +export type Entity = Property | Item | Lexeme export type EntityPageTitle = NamespacedEntityId | ItemId export type Entities = Record @@ -56,8 +56,9 @@ export interface Lexeme extends EntityInfo { lexicalCategory: ItemId language: ItemId lemmas?: Lemmas - forms?: Forms - senses?: Senses + claims?: Claims + forms?: Form[] + senses?: Sense[] } export interface EntityInfo { @@ -70,37 +71,38 @@ export interface EntityInfo { } export interface SimplifiedEntityInfo { - id: EntityId modified?: string } export interface SimplifiedItem extends SimplifiedEntityInfo { + id: ItemId, type: 'item', labels?: SimplifiedLabels descriptions?: SimplifiedDescriptions aliases?: SimplifiedAliases claims?: SimplifiedClaims sitelinks?: SimplifiedSitelinks - lexicalCategory: string } export interface SimplifiedProperty extends SimplifiedEntityInfo { + id: PropertyId, type: 'property', datatype: DataType, labels?: SimplifiedLabels descriptions?: SimplifiedDescriptions aliases?: SimplifiedAliases claims?: SimplifiedClaims - lexicalCategory: string } export interface SimplifiedLexeme extends SimplifiedEntityInfo { + id: LexemeId, type: 'lexeme', lexicalCategory: ItemId language: ItemId lemmas?: SimplifiedLemmas - forms?: SimplifiedForms - senses?: SimplifiedSenses + claims?: SimplifiedClaims + forms?: SimplifiedForm[] + senses?: SimplifiedSense[] } export type SimplifiedEntity = SimplifiedProperty | SimplifiedItem | SimplifiedLexeme diff --git a/src/types/lexeme.ts b/src/types/lexeme.ts index dd6f81bc..5ea8632d 100644 --- a/src/types/lexeme.ts +++ b/src/types/lexeme.ts @@ -1,11 +1,8 @@ import type { Claims } from './claim.js' -import type { FormId, ItemId, PropertyId, SenseId } from './entity.js' +import type { FormId, ItemId, SenseId } from './entity.js' import type { SimplifiedClaims } from './simplify_claims.js' import type { Glosses, Representations, SimplifiedGlosses, SimplifiedRepresentations } from './terms.js' -export type Forms = Record -export type Senses = Record - export interface Form { id: FormId representations?: Representations @@ -19,9 +16,6 @@ export interface Sense { claims?: Claims } -export type SimplifiedForms = Record -export type SimplifiedSenses = Record - export interface SimplifiedForm { id: FormId representations?: SimplifiedRepresentations diff --git a/src/types/simplify_claims.ts b/src/types/simplify_claims.ts index 1e6b665f..a3c12cdc 100644 --- a/src/types/simplify_claims.ts +++ b/src/types/simplify_claims.ts @@ -1,6 +1,6 @@ import type { DataType, Rank } from './claim.js' import type { Guid, PropertyId } from './entity.js' -import type { timeConverters } from '../helpers/parse_claim.js' +import type { TimeConverter, TimeConverterFn } from '../helpers/wikibase_time.js' export interface SimplifySnakOptions { entityPrefix?: string @@ -14,7 +14,7 @@ export interface SimplifySnakOptions { keepRanks?: boolean keepSnaktypes?: boolean keepAll?: boolean - timeConverter?: keyof typeof timeConverters + timeConverter?: TimeConverter | TimeConverterFn novalueValue?: any somevalueValue?: any } diff --git a/src/types/sitelinks.ts b/src/types/sitelinks.ts index 97b84bfe..cced3b39 100644 --- a/src/types/sitelinks.ts +++ b/src/types/sitelinks.ts @@ -17,4 +17,4 @@ export interface Sitelink { export type Sitelinks = Partial> -export type SimplifiedSitelinks = Partial> +export type SimplifiedSitelinks = Partial> diff --git a/src/types/sparql.ts b/src/types/sparql.ts index 570b3d9d..e77e597c 100644 --- a/src/types/sparql.ts +++ b/src/types/sparql.ts @@ -1,13 +1,22 @@ export type SparqlValueRaw = string | number | boolean export type SparqlValueType = SparqlValueRaw | Record +export interface SparqlValueObj { + readonly type: 'uri' | 'bnode' + readonly datatype?: string + readonly value: string +} + export interface SparqlResults { head: { vars: string[] } results: { - bindings: unknown[] + bindings: Record[] } } -export type SimplifiedSparqlResults = Record[] | SparqlValueRaw[] +export type SimplifiedSparqlResultRecord = Record[] +export type SimplifiedSparqlResultMinified = SparqlValueRaw[] + +export type SimplifiedSparqlResults = SimplifiedSparqlResultRecord | SimplifiedSparqlResultMinified diff --git a/src/types/terms.ts b/src/types/terms.ts index 327b2130..c2d8bf82 100644 --- a/src/types/terms.ts +++ b/src/types/terms.ts @@ -1,6 +1,6 @@ import type { WmLanguageCode } from './options.js' -type WmLanguageRecord = Partial>> +type WmLanguageRecord = Readonly>> export type Term = { readonly language: WmLanguageCode diff --git a/src/utils/utils.ts b/src/utils/utils.ts index ad30c692..6ebc51bd 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -56,3 +56,14 @@ export function rejectObsoleteInterface (args: IArguments): void { export function isOfType (all: readonly T[], element: unknown): element is T { return typeof element === 'string' && (all as readonly string[]).includes(element) } + +/** key is a key on the object */ +export function isAKey (obj: Readonly>>, key: unknown): key is T { + return typeof key === 'string' && Object.keys(obj).includes(key) +} + +/** like Object.entries() but with typed key */ +export function typedEntries (input: Readonly>>): Array<[K, V]> { + // @ts-expect-error string is not assignable to K as K is more specific + return Object.entries(input) +} diff --git a/src/wikibase-sdk.ts b/src/wikibase-sdk.ts index 03bda2b8..34078804 100644 --- a/src/wikibase-sdk.ts +++ b/src/wikibase-sdk.ts @@ -3,6 +3,7 @@ import * as parse from './helpers/parse_responses.js' import * as rankHelpers from './helpers/rank.js' import * as simplify from './helpers/simplify.js' import * as sitelinksHelpers from './helpers/sitelinks.js' +import * as timeHelpers from './helpers/wikibase_time.js' import { cirrusSearchPagesFactory } from './queries/cirrus_search.js' import { getEntitiesFactory } from './queries/get_entities.js' import { getEntitiesFromSitelinksFactory } from './queries/get_entities_from_sitelinks.js' @@ -24,8 +25,9 @@ const common = { simplify, parse, ...helpers, - ...sitelinksHelpers, ...rankHelpers, + ...sitelinksHelpers, + ...timeHelpers, } as const type ApiQueries = { diff --git a/tests/cirrus_search.ts b/tests/cirrus_search.ts index af10e363..5e242c08 100644 --- a/tests/cirrus_search.ts +++ b/tests/cirrus_search.ts @@ -8,9 +8,9 @@ const cirrusSearchPages = cirrusSearchPagesFactory(buildUrl) describe('cirrusSearchPages', () => { it('should generate a URL with the query/search endpoint', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello' })) - query.action.should.equal('query') - query.list.should.equal('search') - query.srsearch.should.equal('hello') + should(query.action).equal('query') + should(query.list).equal('search') + should(query.srsearch).equal('hello') should(query.srlimit).not.be.ok() should(query.srnamespace).not.be.ok() should(query.sroffset).not.be.ok() @@ -19,42 +19,43 @@ describe('cirrusSearchPages', () => { }) it('should accept only the object interface', () => { - cirrusSearchPages.bind(null, 'hello').should.throw() + // @ts-expect-error invalid argument + should(() => cirrusSearchPages('hello')).throw() }) describe('haswbstatement', () => { it('should accept a statement argument', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', haswbstatement: 'P31=Q5' })) - query.srsearch.should.equal('hello haswbstatement:P31=Q5') + should(query.srsearch).equal('hello haswbstatement:P31=Q5') }) it('should accept a statement argument alone', () => { const query = parseUrlQuery(cirrusSearchPages({ haswbstatement: 'P31=Q5' })) - query.srsearch.should.equal('haswbstatement:P31=Q5') + should(query.srsearch).equal('haswbstatement:P31=Q5') }) it('should accept an array of statements', () => { const query = parseUrlQuery(cirrusSearchPages({ haswbstatement: [ 'P31=Q5', 'P279=Q2934' ] })) - query.srsearch.should.equal('haswbstatement:P31=Q5 haswbstatement:P279=Q2934') + should(query.srsearch).equal('haswbstatement:P31=Q5 haswbstatement:P279=Q2934') }) it('should accept negative statements', () => { const query = parseUrlQuery(cirrusSearchPages({ haswbstatement: '-P31=Q5' })) - query.srsearch.should.equal('-haswbstatement:P31=Q5') + should(query.srsearch).equal('-haswbstatement:P31=Q5') const query2 = parseUrlQuery(cirrusSearchPages({ haswbstatement: [ 'P31=Q5', '-P279=Q2934' ] })) - query2.srsearch.should.equal('haswbstatement:P31=Q5 -haswbstatement:P279=Q2934') + should(query2.srsearch).equal('haswbstatement:P31=Q5 -haswbstatement:P279=Q2934') }) }) describe('format', () => { it('should default to json', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello' })) - query.format.should.equal('json') + should(query.format).equal('json') }) it('should accept a custom format', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', format: 'xml' })) - query.format.should.equal('xml') + should(query.format).equal('xml') }) }) @@ -66,26 +67,26 @@ describe('cirrusSearchPages', () => { it('should accept a single namespace number', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', namespace: 0 })) - query.srnamespace.should.equal('0') + should(query.srnamespace).equal('0') }) it('should accept a single namespace string', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', namespace: '0' })) - query.srnamespace.should.equal('0') + should(query.srnamespace).equal('0') }) it('should accept multiple namespaces as a string', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', namespace: '0|1' })) - query.srnamespace.should.equal('0|1') + should(query.srnamespace).equal('0|1') }) it('should accept multiple namespaces as an array', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', namespace: [ 0, 1 ] })) - query.srnamespace.should.equal('0|1') + should(query.srnamespace).equal('0|1') }) it('should reject an invalid namespace', () => { - cirrusSearchPages.bind(null, { search: 'hello', namespace: 'foo' }).should.throw(/invalid namespace/) + should(() => cirrusSearchPages({ search: 'hello', namespace: 'foo' })).throw(/invalid namespace/) }) }) @@ -97,11 +98,11 @@ describe('cirrusSearchPages', () => { it('should accept a custom limit', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', limit: 10 })) - query.srlimit.should.equal('10') + should(query.srlimit).equal('10') }) it('should reject an invalid limit', () => { - cirrusSearchPages.bind(null, { search: 'hello', limit: 'foo' }).should.throw(/invalid limit/) + should(() => cirrusSearchPages({ search: 'hello', limit: 'foo' })).throw(/invalid limit/) }) }) @@ -113,11 +114,11 @@ describe('cirrusSearchPages', () => { it('should accept a custom offset', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', offset: 10 })) - query.sroffset.should.equal('10') + should(query.sroffset).equal('10') }) it('should reject an invalid offset', () => { - cirrusSearchPages.bind(null, { search: 'hello', offset: 'foo' }).should.throw(/invalid offset/) + should(() => cirrusSearchPages({ search: 'hello', offset: 'foo' })).throw(/invalid offset/) }) }) @@ -129,11 +130,12 @@ describe('cirrusSearchPages', () => { it('should accept a profile', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', profile: 'wikibase_prefix_boost' })) - query.srqiprofile.should.equal('wikibase_prefix_boost') + should(query.srqiprofile).equal('wikibase_prefix_boost') }) it('should reject an invalid profile', () => { - cirrusSearchPages.bind(null, { search: 'hello', profile: 123 }).should.throw(/invalid profile/) + // @ts-expect-error invalid profile argument + should(() => cirrusSearchPages({ search: 'hello', profile: 123 })).throw(/invalid profile/) }) }) @@ -145,11 +147,12 @@ describe('cirrusSearchPages', () => { it('should accept a sort', () => { const query = parseUrlQuery(cirrusSearchPages({ search: 'hello', sort: 'last_edit_desc' })) - query.srsort.should.equal('last_edit_desc') + should(query.srsort).equal('last_edit_desc') }) it('should reject an invalid sort', () => { - cirrusSearchPages.bind(null, { search: 'hello', sort: 123 }).should.throw(/invalid sort/) + // @ts-expect-error invalid sort argument + should(() => cirrusSearchPages({ search: 'hello', sort: 123 })).throw(/invalid sort/) }) }) }) diff --git a/tests/general.ts b/tests/general.ts index 5bfa7787..39642d9e 100644 --- a/tests/general.ts +++ b/tests/general.ts @@ -4,64 +4,64 @@ import { instance, sparqlEndpoint } from './lib/tests_env.js' describe('builder', () => { it('should be a function', () => { - defaultWBK.should.be.a.Function() - WBK.should.be.a.Function() - defaultWBK.should.equal(WBK) + should(defaultWBK).be.a.Function() + should(WBK).be.a.Function() + should(defaultWBK).equal(WBK) }) it('should reference instance-independant helpers', () => { should(parse).be.an.Object() should(simplify).be.an.Object() - parse.entities.should.be.a.Function() - simplify.labels.should.be.a.Function() - simplifySparqlResults.should.be.a.Function() - isEntityId.should.be.a.Function() - getSitelinkData.should.be.a.Function() + should(parse.entities).be.a.Function() + should(simplify.labels).be.a.Function() + should(simplifySparqlResults).be.a.Function() + should(isEntityId).be.a.Function() + should(getSitelinkData).be.a.Function() }) it('should throw when initialized without a config', () => { - WBK.should.throw() + should(WBK).throw() }) it('should throw when initialized without an instance or a sparqlEndpoint', () => { - (() => WBK({})).should.throw() + should(() => (WBK({}))).throw() }) it('should throw when initialized with an invalid instance', () => { - (() => WBK({ instance: 'foo' })).should.throw('invalid instance: foo') + should(() => (WBK({ instance: 'foo' }))).throw('invalid instance: foo') }) it('should throw when initialized with an invalid sparql endpoint', () => { - (() => WBK({ instance, sparqlEndpoint: 'foo' })).should.throw('invalid sparqlEndpoint: foo') + should(() => (WBK({ instance, sparqlEndpoint: 'foo' }))).throw('invalid sparqlEndpoint: foo') }) it('should not throw when initialized without a sparql endpoint', () => { const wbk = WBK({ instance }) - wbk.sparqlQuery.should.throw('sparqlQuery requires a sparqlEndpoint to be set at initialization') - wbk.getReverseClaims.should.throw('getReverseClaims requires a sparqlEndpoint to be set at initialization') + should(wbk.sparqlQuery).throw('sparqlQuery requires a sparqlEndpoint to be set at initialization') + should(wbk.getReverseClaims).throw('getReverseClaims requires a sparqlEndpoint to be set at initialization') }) it('should not throw when initialized without a sparql endpoint', () => { const wbk = WBK({ sparqlEndpoint }) - wbk.searchEntities.should.throw('searchEntities requires an instance to be set at initialization') - wbk.getEntities.should.throw('getEntities requires an instance to be set at initialization') + should(wbk.searchEntities).throw('searchEntities requires an instance to be set at initialization') + should(wbk.getEntities).throw('getEntities requires an instance to be set at initialization') }) it('should produce valid URLs', () => { const wbk = WBK({ instance, sparqlEndpoint }) - wbk.searchEntities({ search: 'ingmar Bergman' }).should.startWith(instance) - wbk.getReverseClaims({ properties: 'P50', values: 'Q504' }).should.startWith(sparqlEndpoint) + should(wbk.searchEntities({ search: 'ingmar Bergman' })).startWith(instance) + should(wbk.getReverseClaims({ properties: 'P50', values: 'Q504' })).startWith(sparqlEndpoint) }) it('should exposed sanitized instance URL', () => { const wbk = WBK({ instance, sparqlEndpoint }) - wbk.instance.root.should.equal(instance) - wbk.instance.apiEndpoint.should.equal(`${instance}/w/api.php`) + should(wbk.instance.root).equal(instance) + should(wbk.instance.apiEndpoint).equal(`${instance}/w/api.php`) }) it('should allow to customize the script path', () => { - WBK({ instance, wgScriptPath: 'foo' }).instance.apiEndpoint.should.equal(`${instance}/foo/api.php`) - WBK({ instance, wgScriptPath: '/foo' }).instance.apiEndpoint.should.equal(`${instance}/foo/api.php`) + should(WBK({ instance, wgScriptPath: 'foo' }).instance.apiEndpoint).equal(`${instance}/foo/api.php`) + should(WBK({ instance, wgScriptPath: '/foo' }).instance.apiEndpoint).equal(`${instance}/foo/api.php`) }) }) @@ -69,64 +69,64 @@ describe('index', () => { it('should give access to all the function', () => { const wbk = WBK({ instance, sparqlEndpoint }) - wbk.should.be.an.Object() - - wbk.searchEntities.should.be.a.Function() - wbk.cirrusSearchPages.should.be.a.Function() - wbk.getEntities.should.be.a.Function() - wbk.getManyEntities.should.be.a.Function() - wbk.getEntityRevision.should.be.a.Function() - wbk.getReverseClaims.should.be.a.Function() - wbk.getRevisions.should.be.a.Function() - wbk.getEntitiesFromSitelinks.should.be.a.Function() - - wbk.simplify.entity.should.be.a.Function() - wbk.simplify.entities.should.be.a.Function() - wbk.simplify.labels.should.be.a.Function() - wbk.simplify.descriptions.should.be.a.Function() - wbk.simplify.aliases.should.be.a.Function() - wbk.simplify.sitelinks.should.be.a.Function() - wbk.simplify.claim.should.be.a.Function() - wbk.simplify.propertyClaims.should.be.a.Function() - wbk.simplify.claims.should.be.a.Function() - wbk.simplify.snak.should.be.a.Function() - wbk.simplify.propertySnaks.should.be.a.Function() - wbk.simplify.snaks.should.be.a.Function() - wbk.simplify.qualifier.should.be.a.Function() - wbk.simplify.propertyQualifiers.should.be.a.Function() - wbk.simplify.qualifiers.should.be.a.Function() - wbk.simplify.forms.should.be.a.Function() - wbk.simplify.form.should.be.a.Function() - wbk.simplify.senses.should.be.a.Function() - wbk.simplify.sense.should.be.a.Function() - wbk.simplify.sparqlResults.should.be.a.Function() - wbk.truthyClaims.should.be.a.Function() - wbk.truthyPropertyClaims.should.be.a.Function() - - wbk.parse.entities.should.be.a.Function() - - wbk.parse.entities.should.be.a.Function() - wbk.parse.pagesTitles.should.be.a.Function() - - wbk.isEntityId.should.be.a.Function() - wbk.isItemId.should.be.a.Function() - wbk.isPropertyId.should.be.a.Function() - wbk.isNumericId.should.be.a.Function() - wbk.isGuid.should.be.a.Function() - wbk.isHash.should.be.a.Function() - wbk.isPropertyClaimsId.should.be.a.Function() - wbk.getNumericId.should.be.a.Function() - wbk.isEntityId.should.be.a.Function() - wbk.isItemId.should.be.a.Function() - wbk.isPropertyId.should.be.a.Function() - wbk.wikibaseTimeToDateObject.should.be.a.Function() - wbk.wikibaseTimeToEpochTime.should.be.a.Function() - wbk.wikibaseTimeToISOString.should.be.a.Function() - wbk.wikibaseTimeToSimpleDay.should.be.a.Function() - wbk.getSitelinkUrl.should.be.a.Function() - wbk.getSitelinkData.should.be.a.Function() - wbk.isSitelinkKey.should.be.a.Function() - wbk.getImageUrl.should.be.a.Function() - wbk.getEntityIdFromGuid.should.be.a.Function() + should(wbk).be.an.Object() + + should(wbk.searchEntities).be.a.Function() + should(wbk.cirrusSearchPages).be.a.Function() + should(wbk.getEntities).be.a.Function() + should(wbk.getManyEntities).be.a.Function() + should(wbk.getEntityRevision).be.a.Function() + should(wbk.getReverseClaims).be.a.Function() + should(wbk.getRevisions).be.a.Function() + should(wbk.getEntitiesFromSitelinks).be.a.Function() + + should(wbk.simplify.entity).be.a.Function() + should(wbk.simplify.entities).be.a.Function() + should(wbk.simplify.labels).be.a.Function() + should(wbk.simplify.descriptions).be.a.Function() + should(wbk.simplify.aliases).be.a.Function() + should(wbk.simplify.sitelinks).be.a.Function() + should(wbk.simplify.claim).be.a.Function() + should(wbk.simplify.propertyClaims).be.a.Function() + should(wbk.simplify.claims).be.a.Function() + should(wbk.simplify.snak).be.a.Function() + should(wbk.simplify.propertySnaks).be.a.Function() + should(wbk.simplify.snaks).be.a.Function() + should(wbk.simplify.qualifier).be.a.Function() + should(wbk.simplify.propertyQualifiers).be.a.Function() + should(wbk.simplify.qualifiers).be.a.Function() + should(wbk.simplify.forms).be.a.Function() + should(wbk.simplify.form).be.a.Function() + should(wbk.simplify.senses).be.a.Function() + should(wbk.simplify.sense).be.a.Function() + should(wbk.simplify.sparqlResults).be.a.Function() + should(wbk.truthyClaims).be.a.Function() + should(wbk.truthyPropertyClaims).be.a.Function() + + should(wbk.parse.entities).be.a.Function() + + should(wbk.parse.entities).be.a.Function() + should(wbk.parse.pagesTitles).be.a.Function() + + should(wbk.isEntityId).be.a.Function() + should(wbk.isItemId).be.a.Function() + should(wbk.isPropertyId).be.a.Function() + should(wbk.isNumericId).be.a.Function() + should(wbk.isGuid).be.a.Function() + should(wbk.isHash).be.a.Function() + should(wbk.isPropertyClaimsId).be.a.Function() + should(wbk.getNumericId).be.a.Function() + should(wbk.isEntityId).be.a.Function() + should(wbk.isItemId).be.a.Function() + should(wbk.isPropertyId).be.a.Function() + should(wbk.wikibaseTimeToDateObject).be.a.Function() + should(wbk.wikibaseTimeToEpochTime).be.a.Function() + should(wbk.wikibaseTimeToISOString).be.a.Function() + should(wbk.wikibaseTimeToSimpleDay).be.a.Function() + should(wbk.getSitelinkUrl).be.a.Function() + should(wbk.getSitelinkData).be.a.Function() + should(wbk.isSitelinkKey).be.a.Function() + should(wbk.getImageUrl).be.a.Function() + should(wbk.getEntityIdFromGuid).be.a.Function() }) }) diff --git a/tests/get_entities.ts b/tests/get_entities.ts index 768d9973..79907248 100644 --- a/tests/get_entities.ts +++ b/tests/get_entities.ts @@ -9,7 +9,7 @@ describe('wikidata getEntities', () => { describe('polymorphism', () => { it('rejects parameters as multiple arguments', () => { // @ts-expect-error - (() => getEntities('Q1', 'fr', 'info', 'json')).should.throw() + should(() => (getEntities('Q1', 'fr', 'info', 'json'))).throw() }) it('accepts parameters as a unique object argument', () => { @@ -20,38 +20,39 @@ describe('wikidata getEntities', () => { format: 'json', }) const query = parseUrlQuery(url) - query.ids.should.equal('Q1') - query.languages.should.equal('fr') - query.props.should.equal('info') - query.format.should.equal('json') + should(query.ids).equal('Q1') + should(query.languages).equal('fr') + should(query.props).equal('info') + should(query.format).equal('json') }) }) describe('action', () => { it('action should be wbgetentities', () => { const query = parseUrlQuery(getEntities({ ids: [ 'Q1' ] })) - query.action.should.equal('wbgetentities') + should(query.action).equal('wbgetentities') }) }) describe('ids', () => { it('should reject invalid ids', () => { - getEntities.bind(null, { ids: 'foo' }).should.throw('invalid entity id: foo') + // @ts-expect-error invalid id + should(() => getEntities({ ids: 'foo' })).throw('invalid entity id: foo') }) it('accepts one id as a string', () => { const query = parseUrlQuery(getEntities({ ids: 'Q535' })) - query.ids.should.equal('Q535') + should(query.ids).equal('Q535') }) it('accepts ids as an array', () => { const query = parseUrlQuery(getEntities({ ids: [ 'Q535', 'Q7546' ] })) - query.ids.should.equal('Q535|Q7546') + should(query.ids).equal('Q535|Q7546') }) it('accepts all supported entities types', () => { const query = parseUrlQuery(getEntities({ ids: [ 'Q535', 'P123', 'L525' ] })) - query.ids.should.equal('Q535|P123|L525') + should(query.ids).equal('Q535|P123|L525') }) }) @@ -63,12 +64,12 @@ describe('wikidata getEntities', () => { it('accepts one language as a string', () => { const query = parseUrlQuery(getEntities({ ids: 'Q535', languages: 'fr' })) - query.languages.should.equal('fr') + should(query.languages).equal('fr') }) it('accepts language as an array', () => { const query = parseUrlQuery(getEntities({ ids: 'Q535', languages: [ 'fr', 'de' ] })) - query.languages.should.equal('fr|de') + should(query.languages).equal('fr|de') }) }) @@ -79,18 +80,18 @@ describe('wikidata getEntities', () => { }) it('include the requested property', () => { const query = parseUrlQuery(getEntities({ ids: 'Q702741', props: 'claims' })) - query.props.should.equal('claims') + should(query.props).equal('claims') }) it('include the requested properties', () => { const query = parseUrlQuery(getEntities({ ids: 'Q702741', props: [ 'claims', 'info' ] })) - query.props.should.equal('claims|info') + should(query.props).equal('claims|info') }) }) describe('format', () => { it('defaults to json', () => { const query = parseUrlQuery(getEntities({ ids: 'Q535' })) - query.format.should.equal('json') + should(query.format).equal('json') }) }) @@ -102,7 +103,7 @@ describe('wikidata getEntities', () => { it('should add a redirects parameter if false', () => { const query = parseUrlQuery(getEntities({ ids: 'Q535', redirects: false })) - query.redirects.should.equal('no') + should(query.redirects).equal('no') }) }) }) diff --git a/tests/get_entities_from_sitelinks.ts b/tests/get_entities_from_sitelinks.ts index ab916e6c..41ac7feb 100644 --- a/tests/get_entities_from_sitelinks.ts +++ b/tests/get_entities_from_sitelinks.ts @@ -9,51 +9,51 @@ describe('getEntitiesFromSitelinks', () => { describe('polymorphism', () => { it('rejects parameters as multiple arguments', () => { // @ts-expect-error - (() => getEntitiesFromSitelinks('Lyon')).should.throw() + should(() => (getEntitiesFromSitelinks('Lyon'))).throw() // @ts-expect-error - ;(() => getEntitiesFromSitelinks('Lyon', 'en')).should.throw() + should(() => (getEntitiesFromSitelinks('Lyon', 'en'))).throw() }) }) describe('action', () => { it('action should be wbgetentities', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon' })) - query.action.should.equal('wbgetentities') + should(query.action).equal('wbgetentities') }) }) describe('titles', () => { it('accepts one title as a string', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon' })) - query.titles.should.equal('Lyon') + should(query.titles).equal('Lyon') }) it('accepts titles as an array', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: [ 'Lyon', 'Hamburg' ] })) - query.titles.should.equal('Lyon|Hamburg') + should(query.titles).equal('Lyon|Hamburg') }) }) describe('sitelinks', () => { it('accepts one site as a string', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon', sites: 'itwiki' })) - query.sites.should.equal('itwiki') + should(query.sites).equal('itwiki') }) it('accepts titles as an array', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon', sites: [ 'itwiki', 'eswikisource' ] })) - query.sites.should.equal('itwiki|eswikisource') + should(query.sites).equal('itwiki|eswikisource') }) it('defaults to the English Wikipedia', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon' })) - query.sites.should.equal('enwiki') + should(query.sites).equal('enwiki') }) it('converts 2-letters language codes to Wikipedia sites', () => { // @ts-expect-error const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon', sites: [ 'it', 'fr' ] })) - query.sites.should.equal('itwiki|frwiki') + should(query.sites).equal('itwiki|frwiki') }) }) @@ -65,12 +65,12 @@ describe('getEntitiesFromSitelinks', () => { it('accepts one language as a string', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon', languages: 'fr' })) - query.languages.should.equal('fr') + should(query.languages).equal('fr') }) it('accepts language as an array', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon', languages: [ 'fr', 'de' ] })) - query.languages.should.equal('fr|de') + should(query.languages).equal('fr|de') }) }) @@ -84,7 +84,7 @@ describe('getEntitiesFromSitelinks', () => { describe('format', () => { it('default to json', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon' })) - query.format.should.equal('json') + should(query.format).equal('json') }) }) @@ -96,7 +96,7 @@ describe('getEntitiesFromSitelinks', () => { it('should add a redirects parameter if false', () => { const query = parseUrlQuery(getEntitiesFromSitelinks({ titles: 'Lyon', redirects: false })) - query.redirects.should.equal('no') + should(query.redirects).equal('no') }) }) }) diff --git a/tests/get_entity_revision.ts b/tests/get_entity_revision.ts index 072f223c..28cbe591 100644 --- a/tests/get_entity_revision.ts +++ b/tests/get_entity_revision.ts @@ -1,4 +1,4 @@ -import 'should' +import should from 'should' import { getEntityRevisionFactory } from '../src/queries/get_entity_revision.js' import { instance, wgScriptPath } from './lib/tests_env.js' import { parseQuery } from './lib/utils.js' @@ -8,31 +8,31 @@ const getEntityRevision = getEntityRevisionFactory(instance, wgScriptPath) describe('getEntityRevision', () => { it('should reject an invalid entity id', () => { // @ts-expect-error - (() => getEntityRevision({ id: '3548931' })).should.throw('invalid entity id: 3548931') + should(() => (getEntityRevision({ id: '3548931' }))).throw('invalid entity id: 3548931') }) it('should reject an invalid revision', () => { // @ts-expect-error - (() => getEntityRevision({ id: 'Q123', revision: 'foo' })).should.throw('invalid revision id: foo') + should(() => (getEntityRevision({ id: 'Q123', revision: 'foo' }))).throw('invalid revision id: foo') }) it('should return an entity revision url [multiple args interface]', () => { const url = getEntityRevision({ id: 'Q3548931', revision: '3548931' }) - url.should.be.a.String() + should(url).be.a.String() const [ host, query ] = url.split('?') const parsedQuery = parseQuery(query) - host.should.equal('https://www.wikidata.org/w/index.php') - parsedQuery.title.should.equal('Special:EntityData/Q3548931.json') - parsedQuery.revision.should.equal('3548931') + should(host).equal('https://www.wikidata.org/w/index.php') + should(parsedQuery.title).equal('Special:EntityData/Q3548931.json') + should(parsedQuery.revision).equal('3548931') }) it('should return an entity revision url [object interface]', () => { const url = getEntityRevision({ id: 'Q3548931', revision: '3548931' }) - url.should.be.a.String() + should(url).be.a.String() const [ host, query ] = url.split('?') const parsedQuery = parseQuery(query) - host.should.equal('https://www.wikidata.org/w/index.php') - parsedQuery.title.should.equal('Special:EntityData/Q3548931.json') - parsedQuery.revision.should.equal('3548931') + should(host).equal('https://www.wikidata.org/w/index.php') + should(parsedQuery.title).equal('Special:EntityData/Q3548931.json') + should(parsedQuery.revision).equal('3548931') }) }) diff --git a/tests/get_many_entities.ts b/tests/get_many_entities.ts index 1ca01d62..d66aa8f4 100644 --- a/tests/get_many_entities.ts +++ b/tests/get_many_entities.ts @@ -11,20 +11,21 @@ const manyIds = range(1, 80).map(id => `Q${id}` as ItemId) describe('wikidata getManyEntities', () => { describe('general', () => { it('should reject invalid ids', () => { - getManyEntities.bind(null, { ids: [ 'foo' ] }).should.throw('invalid entity id: foo') + // @ts-expect-error not ids + should(() => getManyEntities({ ids: [ 'foo' ] })).throw('invalid entity id: foo') }) it('should return an array of urls', () => { const urls = getManyEntities({ ids: manyIds }) - urls.should.be.an.Array() - urls.forEach(url => /^https/.test(url).should.be.true()) + should(urls).be.an.Array() + urls.forEach(url => should(/^https/.test(url)).be.true()) }) }) describe('polymorphism', () => { it('should reject parameters as multiple arguments', () => { // @ts-expect-error - (() => getManyEntities(manyIds, 'fr', 'info', 'json')).should.throw() + should(() => (getManyEntities(manyIds, 'fr', 'info', 'json'))).throw() }) it('should accept parameters as a unique object argument', () => { @@ -34,13 +35,13 @@ describe('wikidata getManyEntities', () => { props: 'labels', format: 'xml', }) - urls.should.be.an.Array() + should(urls).be.an.Array() urls.forEach(url => { const query = parseUrlQuery(url) - query.ids.should.startWith('Q') - query.languages.should.equal('fr') - query.props.should.equal('labels') - query.format.should.equal('xml') + should(query.ids).startWith('Q') + should(query.languages).equal('fr') + should(query.props).equal('labels') + should(query.format).equal('xml') }) }) }) @@ -48,7 +49,7 @@ describe('wikidata getManyEntities', () => { describe('ids', () => { it('should throw if passed an id string', () => { // @ts-expect-error - (() => getManyEntities({ ids: 'Q535' })).should.throw() + should(() => (getManyEntities({ ids: 'Q535' }))).throw() }) }) @@ -61,7 +62,7 @@ describe('wikidata getManyEntities', () => { it('should add a redirects parameter if false', () => { const urls = getManyEntities({ ids: [ 'Q535' ], redirects: false }) const url = urls[0] as string - parseUrlQuery(url).redirects.should.equal('no') + should(parseUrlQuery(url).redirects).equal('no') }) }) }) diff --git a/tests/get_reverse_claims.ts b/tests/get_reverse_claims.ts index 8adb7372..896a116f 100644 --- a/tests/get_reverse_claims.ts +++ b/tests/get_reverse_claims.ts @@ -6,18 +6,18 @@ const getReverseClaims = getReverseClaimsFactory(sparqlEndpoint) describe('getReverseClaims', () => { it('env', () => { - getReverseClaims.should.be.a.Function() + should(getReverseClaims).be.a.Function() }) it('should reject invalid property ids', () => { // @ts-expect-error - (() => getReverseClaims({ properties: 'foo', values: 'Q535' })).should.throw('invalid property id: foo') + should(() => (getReverseClaims({ properties: 'foo', values: 'Q535' }))).throw('invalid property id: foo') }) it('should return a SPARQL query url', () => { const url = getReverseClaims({ properties: 'P50', values: 'Q535' }) - url.should.be.a.String() - url.startsWith('https://query.wikidata.org').should.be.exactly(true) + should(url).be.a.String() + should(url.startsWith('https://query.wikidata.org')).be.exactly(true) should(url.match(/SELECT/)).be.ok() should(url.match(/WHERE/)).be.ok() should(url.match(/LIMIT/)).not.be.ok() @@ -31,8 +31,8 @@ describe('getReverseClaims', () => { it('should return a SPARQL query with filter for insensitive case', () => { const url = getReverseClaims({ properties: 'P2002', values: 'BouletCorp', caseInsensitive: true }) - url.should.be.a.String() - url.startsWith('https://query.wikidata.org').should.be.exactly(true) + should(url).be.a.String() + should(url.startsWith('https://query.wikidata.org')).be.exactly(true) should(url.match(/SELECT/)).be.ok() should(url.match(/WHERE/)).be.ok() should(url.match(/LIMIT/)).not.be.ok() @@ -51,21 +51,21 @@ describe('getReverseClaims', () => { it('should allow to request subjects for several properties at once', () => { const url = getReverseClaims({ properties: [ 'P50', 'P110' ], values: 'Q281411' }) - url.should.match(/wdt%3AP50%7Cwdt%3AP110/) + should(url).match(/wdt%3AP50%7Cwdt%3AP110/) }) it('should allow to request subjects for several values at once', () => { const url = getReverseClaims({ properties: 'P50', values: [ 'Q281411', 'Q206685' ] }) - url.should.match(/UNION/) + should(url).match(/UNION/) const url2 = getReverseClaims({ properties: 'P2002', values: [ 'wikicite', 'slpng_giants' ], caseInsensitive: true }) - url2.should.match(/UNION/) + should(url2).match(/UNION/) }) // Doing both a UNION and piping properties fails // Ex: https://query.wikidata.org/#SELECT%20DISTINCT%20%3Fsubject%20WHERE%20%7B%0A%20%20%7B%0A%20%20%20%20%3Fsubject%20wdt%3AP50%7Cwdt%3AP110%20wd%3AQ281411%20.%0A%20%20%20%20FILTER%20NOT%20EXISTS%20%7B%20%3Fsubject%20rdf%3Atype%20wikibase%3AProperty%20.%20%7D%0A%20%20%7D%20UNION%20%7B%0A%20%20%20%20%3Fsubject%20wdt%3AP50%7Cwdt%3AP110%20wd%3AQ206685%20.%0A%20%20%20%20FILTER%20NOT%20EXISTS%20%7B%20%3Fsubject%20rdf%3Atype%20wikibase%3AProperty%20.%20%7D%20%0A%20%20%7D%0A%7D%0ALIMIT%201000 // it('should allow to request subjects for several properties and values at once', () => { // const url = getReverseClaims([ 'P50', 'P110' ], [ 'Q281411', 'Q206685' ]) - // url.match(/wdt%3AP50%7Cwdt%3AP110/g).length.should.equal(2) - // url.should.match(/UNION/) + // should(url.match(/wdt%3AP50%7Cwdt%3AP110/g).length).equal(2) + // should(url).match(/UNION/) // }) }) diff --git a/tests/get_revisions.ts b/tests/get_revisions.ts index d5f9e883..ca69c1a2 100644 --- a/tests/get_revisions.ts +++ b/tests/get_revisions.ts @@ -10,61 +10,61 @@ const getRevisions = getRevisionsFactory(buildUrl) describe('getRevisions', () => { it('should reject invalid ids', () => { // @ts-expect-error - (() => getRevisions({ ids: 'foo' })).should.throw('invalid entity page title: foo') + should(() => (getRevisions({ ids: 'foo' }))).throw('invalid entity page title: foo') }) it('should accept namespaced ids invalid ids', () => { - (() => getRevisions({ ids: 'Item:Q123' })).should.not.throw() - ;(() => getRevisions({ ids: 'Property:P123' })).should.not.throw() - ;(() => getRevisions({ ids: 'Lexeme:L123' })).should.not.throw() + should(() => (getRevisions({ ids: 'Item:Q123' }))).not.throw() + should(() => (getRevisions({ ids: 'Property:P123' }))).not.throw() + should(() => (getRevisions({ ids: 'Lexeme:L123' }))).not.throw() // @ts-expect-error title is invalid - ;(() => getRevisions({ ids: 'Property:Q123' })).should.throw('invalid entity page title: Property:Q123') + should(() => (getRevisions({ ids: 'Property:Q123' }))).throw('invalid entity page title: Property:Q123') }) it('should return a revision query url', () => { const url = getRevisions({ ids: 'Q3548931' }) - url.should.be.a.String() + should(url).be.a.String() const query = parseQuery(url.split('?')[1]) - query.action.should.equal('query') - query.prop.should.equal('revisions') - query.titles.should.equal('Q3548931') - query.rvlimit.should.equal('max') - query.format.should.equal('json') - query.rvprop.should.equal('ids|flags|timestamp|user|userid|size|slotsize|sha1|slotsha1|contentmodel|comment|parsedcomment|content|tags|roles|oresscores') - query.rvslots.should.equal('*') + should(query.action).equal('query') + should(query.prop).equal('revisions') + should(query.titles).equal('Q3548931') + should(query.rvlimit).equal('max') + should(query.format).equal('json') + should(query.rvprop).equal('ids|flags|timestamp|user|userid|size|slotsize|sha1|slotsha1|contentmodel|comment|parsedcomment|content|tags|roles|oresscores') + should(query.rvslots).equal('*') }) it('should accept several ids', () => { const url = getRevisions({ ids: [ 'Q3548931', 'Q3548932' ] }) const query = parseQuery(url.split('?')[1]) - query.titles.should.equal('Q3548931|Q3548932') + should(query.titles).equal('Q3548931|Q3548932') }) it('should accept custom parameters', () => { const url = getRevisions({ ids: 'Q3548931', limit: 2, start: sinceYesterdayInSeconds }) const query = parseQuery(url.split('?')[1]) - query.rvlimit.should.equal('2') - query.rvstart.should.equal(sinceYesterdayInSeconds.toString()) + should(query.rvlimit).equal('2') + should(query.rvstart).equal(sinceYesterdayInSeconds.toString()) }) it('should accept time in milliseconds', () => { const url = getRevisions({ ids: 'Q3548931', start: sinceYesterdayInMilliSeconds }) const query = parseQuery(url.split('?')[1]) - query.rvstart.should.equal(sinceYesterdayInSeconds.toString()) + should(query.rvstart).equal(sinceYesterdayInSeconds.toString()) }) it('should accept time in ISO format', () => { const ISOtime = new Date(sinceYesterdayInMilliSeconds).toISOString() const url = getRevisions({ ids: 'Q3548931', end: ISOtime }) const query = parseQuery(url.split('?')[1]) - query.rvend.should.equal(sinceYesterdayInSeconds.toString()) + should(query.rvend).equal(sinceYesterdayInSeconds.toString()) }) it('should accept date objects in ISO format', () => { const dateObj = new Date(sinceYesterdayInMilliSeconds) const url = getRevisions({ ids: 'Q3548931', end: dateObj }) const query = parseQuery(url.split('?')[1]) - query.rvend.should.equal(sinceYesterdayInSeconds.toString()) + should(query.rvend).equal(sinceYesterdayInSeconds.toString()) }) it('should ignore parameters that the API refuses for multiple ids', () => { @@ -77,30 +77,30 @@ describe('getRevisions', () => { it('should allow to set rvprop as a string', () => { const url = getRevisions({ ids: 'Q3548931', prop: 'tags|user' }) const query = parseQuery(url.split('?')[1]) - query.rvprop.should.equal('tags|user') + should(query.rvprop).equal('tags|user') }) it('should allow to set rvprop as an array', () => { const url = getRevisions({ ids: 'Q3548931', prop: [ 'tags', 'user' ] }) const query = parseQuery(url.split('?')[1]) - query.rvprop.should.equal('tags|user') + should(query.rvprop).equal('tags|user') }) it('should allow to set rvuser', () => { const url = getRevisions({ ids: 'Q3548931', user: 'foo' }) const query = parseQuery(url.split('?')[1]) - query.rvuser.should.equal('foo') + should(query.rvuser).equal('foo') }) it('should allow to set rvexcludeuser', () => { const url = getRevisions({ ids: 'Q3548931', excludeuser: 'foo' }) const query = parseQuery(url.split('?')[1]) - query.rvexcludeuser.should.equal('foo') + should(query.rvexcludeuser).equal('foo') }) it('should allow to set rvtag', () => { const url = getRevisions({ ids: 'Q3548931', tag: 'foo' }) const query = parseQuery(url.split('?')[1]) - query.rvtag.should.equal('foo') + should(query.rvtag).equal('foo') }) }) diff --git a/tests/helpers.ts b/tests/helpers.ts index ad3d7dee..23206991 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -1,5 +1,4 @@ -import 'should' - +import should from 'should' import { getEntityIdFromGuid, getImageUrl, @@ -17,341 +16,197 @@ import { isPropertyClaimsId, isPropertyId, isSenseId, - wikibaseTimeToEpochTime, - wikibaseTimeToISOString, - wikibaseTimeToSimpleDay, } from '../src/helpers/helpers.js' -import { readJsonFile } from './lib/utils.js' - -const Q970917 = readJsonFile('./tests/data/Q970917.json') describe('helpers', () => { - const ISOtime = '2014-05-14T00:00:00.000Z' - const wdTime = '+2014-05-14T00:00:00Z' - const epoch = 1400025600000 - const ISOnegativeTime = '-000044-03-15T00:00:00.000Z' - const negativeWdTime = '-0044-03-15T00:00:00Z' - const negativeEpoch = -63549360000000 - - describe('wikibaseTimeToEpochTime', () => { - it('env', () => { - new Date(epoch).toISOString().should.equal(ISOtime) - new Date(negativeEpoch).toISOString().should.equal(ISOnegativeTime) - }) - - it('should return a number (epoch time)', () => { - wikibaseTimeToEpochTime(wdTime).should.be.a.Number() - }) - - it('should return a number for negative time', () => { - wikibaseTimeToEpochTime(negativeWdTime).should.be.a.Number() - }) - - it('should return the right number', () => { - wikibaseTimeToEpochTime(wdTime).should.equal(epoch) - }) - - it('should return the right number for negative time too', () => { - wikibaseTimeToEpochTime(negativeWdTime).should.equal(negativeEpoch) - }) - - it('should accept a value object', () => { - wikibaseTimeToEpochTime(Q970917.claims.P569[0].mainsnak.datavalue.value) - .should.equal(-3160944000000) - wikibaseTimeToEpochTime(Q970917.claims.P569[1].mainsnak.datavalue.value) - .should.equal(657417600000) - wikibaseTimeToEpochTime(Q970917.claims.P569[2].mainsnak.datavalue.value) - .should.equal(631152000000) - }) - }) - - describe('wikibaseTimeToISOString', () => { - it('should convert wikibase date to ISO date', () => { - wikibaseTimeToISOString('+1885-05-22T00:00:00Z') - .should.equal('1885-05-22T00:00:00.000Z') - - wikibaseTimeToISOString('+0180-03-17T00:00:00Z') - .should.equal('0180-03-17T00:00:00.000Z') - - wikibaseTimeToISOString('-0398-00-00T00:00:00Z') - .should.equal('-000398-01-01T00:00:00.000Z') - - wikibaseTimeToISOString('-34000-00-00T00:00:00Z') - .should.equal('-034000-01-01T00:00:00.000Z') - - wikibaseTimeToISOString('+34000-00-00T00:00:00Z') - .should.equal('+034000-01-01T00:00:00.000Z') - }) - - it('should return a valid time for possible invalid dates', () => { - wikibaseTimeToISOString('+1953-00-00T00:00:00Z') - .should.equal('1953-01-01T00:00:00.000Z') - - wikibaseTimeToISOString('+1953-11-00T00:00:00Z') - .should.equal('1953-11-01T00:00:00.000Z') - }) - - it('should return a valid time even for possible invalid negative date', () => { - wikibaseTimeToISOString('-1953-00-00T00:00:00Z') - .should.equal('-001953-01-01T00:00:00.000Z') - - wikibaseTimeToISOString('-1953-11-00T00:00:00Z') - .should.equal('-001953-11-01T00:00:00.000Z') - }) - - it('should return a valid time for dates far in the past', () => { - wikibaseTimeToISOString('-13798000000-00-00T00:00:00Z') - .should.equal('-13798000000-01-01T00:00:00Z') - - wikibaseTimeToISOString('-13798000000-02-00T00:00:00Z') - .should.equal('-13798000000-02-01T00:00:00Z') - - wikibaseTimeToISOString('-13798000000-02-07T15:00:00Z') - .should.equal('-13798000000-02-07T15:00:00Z') - }) - - it('should return a valid time for dates far in the future', () => { - wikibaseTimeToISOString('+13798000000-00-00T00:00:00Z') - .should.equal('+13798000000-01-01T00:00:00Z') - - wikibaseTimeToISOString('+13798000000-02-00T00:00:00Z') - .should.equal('+13798000000-02-01T00:00:00Z') - - wikibaseTimeToISOString('+13798000000-02-07T15:00:00Z') - .should.equal('+13798000000-02-07T15:00:00Z') - }) - - it('should accept a value object', () => { - wikibaseTimeToISOString(Q970917.claims.P569[0].mainsnak.datavalue.value) - .should.equal('1869-11-01T00:00:00.000Z') - wikibaseTimeToISOString(Q970917.claims.P569[1].mainsnak.datavalue.value) - .should.equal('1990-11-01T00:00:00.000Z') - wikibaseTimeToISOString(Q970917.claims.P569[2].mainsnak.datavalue.value) - .should.equal('1990-01-01T00:00:00.000Z') - }) - }) - - describe('wikibaseTimeToSimpleDay', () => { - it('should convert wikibase date with year precision to simple-day', () => { - wikibaseTimeToSimpleDay('+1953-00-00T00:00:00Z').should.equal('1953') - wikibaseTimeToSimpleDay('-1953-00-00T00:00:00Z').should.equal('-1953') - wikibaseTimeToSimpleDay('+13-00-00T00:00:00Z').should.equal('13') - wikibaseTimeToSimpleDay('-13-00-00T00:00:00Z').should.equal('-13') - wikibaseTimeToSimpleDay('-0100-00-00T00:00:00Z').should.equal('-100') - }) - - it('should convert wikibase date with month precision to simple-day', () => { - wikibaseTimeToSimpleDay('+1953-01-00T00:00:00Z').should.equal('1953-01') - wikibaseTimeToSimpleDay('-1953-01-00T00:00:00Z').should.equal('-1953-01') - wikibaseTimeToSimpleDay('+13-01-00T00:00:00Z').should.equal('13-01') - wikibaseTimeToSimpleDay('-13-01-00T00:00:00Z').should.equal('-13-01') - wikibaseTimeToSimpleDay('-0044-03-00T00:00:00Z').should.equal('-44-03') - }) - - it('should convert wikibase date with day precision or finer to simple-day', () => { - wikibaseTimeToSimpleDay('+1953-01-01T00:00:00Z').should.equal('1953-01-01') - wikibaseTimeToSimpleDay('-1953-01-01T00:00:00Z').should.equal('-1953-01-01') - wikibaseTimeToSimpleDay('+1953-01-01T13:45:00Z').should.equal('1953-01-01') - wikibaseTimeToSimpleDay('-1953-01-01T13:45:00Z').should.equal('-1953-01-01') - wikibaseTimeToSimpleDay('-0044-03-01T00:00:00Z').should.equal('-44-03-01') - }) - - it('should accept a value object', () => { - wikibaseTimeToSimpleDay(Q970917.claims.P569[0].mainsnak.datavalue.value) - .should.equal('1869-11') - }) - }) - describe('isEntityId', () => { it('should accept all supported entity types ids', () => { - isEntityId('Q571').should.be.true() - isEntityId('P31').should.be.true() - isEntityId('L525').should.be.true() - isEntityId('L525-F1').should.be.true() - isEntityId('L525-S1').should.be.true() - isEntityId('L525-Z1').should.be.false() - isEntityId('M42').should.be.true() - isEntityId('31').should.be.false() + should(isEntityId('Q571')).be.true() + should(isEntityId('P31')).be.true() + should(isEntityId('L525')).be.true() + should(isEntityId('L525-F1')).be.true() + should(isEntityId('L525-S1')).be.true() + should(isEntityId('L525-Z1')).be.false() + should(isEntityId('M42')).be.true() + should(isEntityId('31')).be.false() // @ts-expect-error non string input - isEntityId(31).should.be.false() - isEntityId('Z31').should.be.false() - isEntityId('q31').should.be.false() - isEntityId('p31').should.be.false() + should(isEntityId(31)).be.false() + should(isEntityId('Z31')).be.false() + should(isEntityId('q31')).be.false() + should(isEntityId('p31')).be.false() }) }) describe('isItemId', () => { it('should accept item ids', () => { - isItemId('Q571').should.be.true() - isItemId('P31').should.be.false() - isItemId('31').should.be.false() + should(isItemId('Q571')).be.true() + should(isItemId('P31')).be.false() + should(isItemId('31')).be.false() // @ts-expect-error non string input - isItemId(31).should.be.false() - isItemId('Z31').should.be.false() - isItemId('q31').should.be.false() - isItemId('p31').should.be.false() + should(isItemId(31)).be.false() + should(isItemId('Z31')).be.false() + should(isItemId('q31')).be.false() + should(isItemId('p31')).be.false() }) }) describe('isPropertyId', () => { it('should accept property ids', () => { - isPropertyId('P31').should.be.true() - isPropertyId('Q571').should.be.false() - isPropertyId('31').should.be.false() + should(isPropertyId('P31')).be.true() + should(isPropertyId('Q571')).be.false() + should(isPropertyId('31')).be.false() // @ts-expect-error non string input - isPropertyId(31).should.be.false() - isPropertyId('Z31').should.be.false() - isPropertyId('q31').should.be.false() - isPropertyId('p31').should.be.false() + should(isPropertyId(31)).be.false() + should(isPropertyId('Z31')).be.false() + should(isPropertyId('q31')).be.false() + should(isPropertyId('p31')).be.false() }) }) describe('isLexemeId', () => { it('should accept lexeme ids', () => { - isLexemeId('L525').should.be.true() - isLexemeId('P31').should.be.false() - isLexemeId('Q571').should.be.false() - isLexemeId('31').should.be.false() + should(isLexemeId('L525')).be.true() + should(isLexemeId('P31')).be.false() + should(isLexemeId('Q571')).be.false() + should(isLexemeId('31')).be.false() // @ts-expect-error non string input - isLexemeId(31).should.be.false() - isLexemeId('Z31').should.be.false() - isLexemeId('q31').should.be.false() - isLexemeId('p31').should.be.false() + should(isLexemeId(31)).be.false() + should(isLexemeId('Z31')).be.false() + should(isLexemeId('q31')).be.false() + should(isLexemeId('p31')).be.false() }) }) describe('isFormId', () => { it('should accept form ids', () => { - isFormId('L525-F1').should.be.true() - isFormId('L525').should.be.false() - isFormId('L525F1').should.be.false() - isFormId('L525-S1').should.be.false() + should(isFormId('L525-F1')).be.true() + should(isFormId('L525')).be.false() + should(isFormId('L525F1')).be.false() + should(isFormId('L525-S1')).be.false() }) }) describe('isSenseId', () => { it('should accept sense ids', () => { - isSenseId('L525-S1').should.be.true() - isSenseId('L525').should.be.false() - isSenseId('L525S1').should.be.false() - isSenseId('L525-F1').should.be.false() + should(isSenseId('L525-S1')).be.true() + should(isSenseId('L525')).be.false() + should(isSenseId('L525S1')).be.false() + should(isSenseId('L525-F1')).be.false() }) }) describe('isMediaInfoId', () => { it('should accept media info ids', () => { - isMediaInfoId('M42').should.be.true() - isMediaInfoId('Q42').should.be.false() - isMediaInfoId('42').should.be.false() + should(isMediaInfoId('M42')).be.true() + should(isMediaInfoId('Q42')).be.false() + should(isMediaInfoId('42')).be.false() }) }) describe('isGuid', () => { it('should accept guids for all supported entities types', () => { - isGuid('q520$BCA8D9DE-B467-473B-943C-6FD0C5B3D02C').should.be.true() - isGuid('Q520$91F0CCEA-19E4-4CEB-97D9-74B014C14686').should.be.true() - isGuid('q520$7f95c04f-4cb6-b018-80eb-fefe0e0bf377').should.be.true() - isGuid('Q520$4a0b85a0-4a47-3254-0379-52680370fec6').should.be.true() - isGuid('L525$faeae005-4b75-1319-5516-e08a8bdd0e9c').should.be.true() - isGuid('L525-F2$52c9b382-02f5-4413-9923-26ade74f5a0d').should.be.true() - isGuid('L525-S1$66D20252-8CEC-4DB1-8B00-D713CFF42E48').should.be.true() - isGuid('P6216$a7fd6230-496e-6b47-ca4a-dcec5dbd7f95').should.be.true() - isGuid('Q520$4a0b85a0-4a47-3254-0379-52680370fec').should.be.false() - isGuid('Q520').should.be.false() + should(isGuid('q520$BCA8D9DE-B467-473B-943C-6FD0C5B3D02C')).be.true() + should(isGuid('Q520$91F0CCEA-19E4-4CEB-97D9-74B014C14686')).be.true() + should(isGuid('q520$7f95c04f-4cb6-b018-80eb-fefe0e0bf377')).be.true() + should(isGuid('Q520$4a0b85a0-4a47-3254-0379-52680370fec6')).be.true() + should(isGuid('L525$faeae005-4b75-1319-5516-e08a8bdd0e9c')).be.true() + should(isGuid('L525-F2$52c9b382-02f5-4413-9923-26ade74f5a0d')).be.true() + should(isGuid('L525-S1$66D20252-8CEC-4DB1-8B00-D713CFF42E48')).be.true() + should(isGuid('P6216$a7fd6230-496e-6b47-ca4a-dcec5dbd7f95')).be.true() + should(isGuid('Q520$4a0b85a0-4a47-3254-0379-52680370fec')).be.false() + should(isGuid('Q520')).be.false() }) }) describe('isHash', () => { it('should accept hash', () => { - isHash('14ddd544b82e2f811669d2bb4c939c4997536ce3').should.be.true() - isHash('14ddd544b82e2f811669d2bb4c939c4997536ce').should.be.false() - isHash('14ddd544b82e2f811669d2bb4c939c4997536ceaf').should.be.false() - isHash('14ddd544b82e2f811669d2bb4c939c4997536ceg').should.be.false() + should(isHash('14ddd544b82e2f811669d2bb4c939c4997536ce3')).be.true() + should(isHash('14ddd544b82e2f811669d2bb4c939c4997536ce')).be.false() + should(isHash('14ddd544b82e2f811669d2bb4c939c4997536ceaf')).be.false() + should(isHash('14ddd544b82e2f811669d2bb4c939c4997536ceg')).be.false() }) }) describe('isPropertyClaimsId', () => { it('should accept property claims ids', () => { - isPropertyClaimsId('Q1#P1').should.be.true() - isPropertyClaimsId('P12#P12').should.be.true() - isPropertyClaimsId('L123#P123').should.be.true() - isPropertyClaimsId('Q1~P1').should.be.false() - isPropertyClaimsId('Q1~Q1').should.be.false() + should(isPropertyClaimsId('Q1#P1')).be.true() + should(isPropertyClaimsId('P12#P12')).be.true() + should(isPropertyClaimsId('L123#P123')).be.true() + should(isPropertyClaimsId('Q1~P1')).be.false() + should(isPropertyClaimsId('Q1~Q1')).be.false() }) }) describe('isEntitySchemaId', () => { it('should accept entity schema ids', () => { - isEntitySchemaId('E123').should.be.true() - isEntitySchemaId('Q123').should.be.false() + should(isEntitySchemaId('E123')).be.true() + should(isEntitySchemaId('Q123')).be.false() }) }) describe('isEntityPageTitle', () => { it('should accept correct titles', () => { - isEntityPageTitle('Item:Q42').should.be.true() - isEntityPageTitle('Lexeme:L42').should.be.true() - isEntityPageTitle('Property:P42').should.be.true() - isEntityPageTitle('Q42').should.be.true() + should(isEntityPageTitle('Item:Q42')).be.true() + should(isEntityPageTitle('Lexeme:L42')).be.true() + should(isEntityPageTitle('Property:P42')).be.true() + should(isEntityPageTitle('Q42')).be.true() - isEntityPageTitle('Item:L42').should.be.false() - isEntityPageTitle('Lexeme:P42').should.be.false() - isEntityPageTitle('Property:Q42').should.be.false() - isEntityPageTitle('P42').should.be.false() + should(isEntityPageTitle('Item:L42')).be.false() + should(isEntityPageTitle('Lexeme:P42')).be.false() + should(isEntityPageTitle('Property:Q42')).be.false() + should(isEntityPageTitle('P42')).be.false() }) }) describe('isNumericId', () => { it('should accept numeric ids', () => { - isNumericId('1').should.be.true() - isNumericId('Q1').should.be.false() + should(isNumericId('1')).be.true() + should(isNumericId('Q1')).be.false() }) }) describe('getNumericId', () => { it('should get a numeric id from an entity id', () => { - getNumericId('Q1').should.equal('1') - getNumericId('P1').should.equal('1') - getNumericId('L1').should.equal('1') - getNumericId('M1').should.equal('1') - getNumericId.bind(null, 'L1-F1').should.throw() - getNumericId.bind(null, 'L1-S1').should.throw() + should(getNumericId('Q1')).equal('1') + should(getNumericId('P1')).equal('1') + should(getNumericId('L1')).equal('1') + should(getNumericId('M1')).equal('1') + should(() => getNumericId('L1-F1')).throw() + should(() => getNumericId('L1-S1')).throw() }) }) describe('getImageUrl', () => { it('should build a commons FilePath Url', () => { - getImageUrl('Peredot.jpg') - .should.equal('https://commons.wikimedia.org/wiki/Special:FilePath/Peredot.jpg') + should(getImageUrl('Peredot.jpg')).equal('https://commons.wikimedia.org/wiki/Special:FilePath/Peredot.jpg') - getImageUrl('Peredot.jpg', 250) - .should.equal('https://commons.wikimedia.org/wiki/Special:FilePath/Peredot.jpg?width=250') + should(getImageUrl('Peredot.jpg', 250)).equal('https://commons.wikimedia.org/wiki/Special:FilePath/Peredot.jpg?width=250') }) }) describe('getEntityIdFromGuid', () => { it('should support all kinds of guids', () => { - getEntityIdFromGuid('q520$BCA8D9DE-B467-473B-943C-6FD0C5B3D02C').should.equal('Q520') - getEntityIdFromGuid('Q520$91F0CCEA-19E4-4CEB-97D9-74B014C14686').should.equal('Q520') - getEntityIdFromGuid('q520$7f95c04f-4cb6-b018-80eb-fefe0e0bf377').should.equal('Q520') - getEntityIdFromGuid('Q520$4a0b85a0-4a47-3254-0379-52680370fec6').should.equal('Q520') - getEntityIdFromGuid('L525$faeae005-4b75-1319-5516-e08a8bdd0e9c').should.equal('L525') - getEntityIdFromGuid('L525-F2$52c9b382-02f5-4413-9923-26ade74f5a0d').should.equal('L525-F2') - getEntityIdFromGuid('L525-S1$66D20252-8CEC-4DB1-8B00-D713CFF42E48').should.equal('L525-S1') - getEntityIdFromGuid('P6216$a7fd6230-496e-6b47-ca4a-dcec5dbd7f95').should.equal('P6216') - getEntityIdFromGuid('Q520$4a0b85a0-4a47-3254-0379-52680370fec').should.equal('Q520') + should(getEntityIdFromGuid('q520$BCA8D9DE-B467-473B-943C-6FD0C5B3D02C')).equal('Q520') + should(getEntityIdFromGuid('Q520$91F0CCEA-19E4-4CEB-97D9-74B014C14686')).equal('Q520') + should(getEntityIdFromGuid('q520$7f95c04f-4cb6-b018-80eb-fefe0e0bf377')).equal('Q520') + should(getEntityIdFromGuid('Q520$4a0b85a0-4a47-3254-0379-52680370fec6')).equal('Q520') + should(getEntityIdFromGuid('L525$faeae005-4b75-1319-5516-e08a8bdd0e9c')).equal('L525') + should(getEntityIdFromGuid('L525-F2$52c9b382-02f5-4413-9923-26ade74f5a0d')).equal('L525-F2') + should(getEntityIdFromGuid('L525-S1$66D20252-8CEC-4DB1-8B00-D713CFF42E48')).equal('L525-S1') + should(getEntityIdFromGuid('P6216$a7fd6230-496e-6b47-ca4a-dcec5dbd7f95')).equal('P6216') + should(getEntityIdFromGuid('Q520$4a0b85a0-4a47-3254-0379-52680370fec')).equal('Q520') }) it('should support hyphenated versions', () => { - getEntityIdFromGuid('q520-BCA8D9DE-B467-473B-943C-6FD0C5B3D02C').should.equal('Q520') - getEntityIdFromGuid('Q520-91F0CCEA-19E4-4CEB-97D9-74B014C14686').should.equal('Q520') - getEntityIdFromGuid('q520-7f95c04f-4cb6-b018-80eb-fefe0e0bf377').should.equal('Q520') - getEntityIdFromGuid('Q520-4a0b85a0-4a47-3254-0379-52680370fec6').should.equal('Q520') - getEntityIdFromGuid('L525-faeae005-4b75-1319-5516-e08a8bdd0e9c').should.equal('L525') - getEntityIdFromGuid('L525-F2-52c9b382-02f5-4413-9923-26ade74f5a0d').should.equal('L525-F2') - getEntityIdFromGuid('L525-S1-66D20252-8CEC-4DB1-8B00-D713CFF42E48').should.equal('L525-S1') - getEntityIdFromGuid('P6216-a7fd6230-496e-6b47-ca4a-dcec5dbd7f95').should.equal('P6216') - getEntityIdFromGuid('Q520-4a0b85a0-4a47-3254-0379-52680370fec').should.equal('Q520') + should(getEntityIdFromGuid('q520-BCA8D9DE-B467-473B-943C-6FD0C5B3D02C')).equal('Q520') + should(getEntityIdFromGuid('Q520-91F0CCEA-19E4-4CEB-97D9-74B014C14686')).equal('Q520') + should(getEntityIdFromGuid('q520-7f95c04f-4cb6-b018-80eb-fefe0e0bf377')).equal('Q520') + should(getEntityIdFromGuid('Q520-4a0b85a0-4a47-3254-0379-52680370fec6')).equal('Q520') + should(getEntityIdFromGuid('L525-faeae005-4b75-1319-5516-e08a8bdd0e9c')).equal('L525') + should(getEntityIdFromGuid('L525-F2-52c9b382-02f5-4413-9923-26ade74f5a0d')).equal('L525-F2') + should(getEntityIdFromGuid('L525-S1-66D20252-8CEC-4DB1-8B00-D713CFF42E48')).equal('L525-S1') + should(getEntityIdFromGuid('P6216-a7fd6230-496e-6b47-ca4a-dcec5dbd7f95')).equal('P6216') + should(getEntityIdFromGuid('Q520-4a0b85a0-4a47-3254-0379-52680370fec')).equal('Q520') }) }) }) diff --git a/tests/parse.ts b/tests/parse.ts index f3df6088..6bb9e60d 100644 --- a/tests/parse.ts +++ b/tests/parse.ts @@ -1,4 +1,4 @@ -import 'should' +import should from 'should' import * as parse from '../src/helpers/parse_responses.js' import { readJsonFile } from './lib/utils.js' @@ -9,25 +9,23 @@ describe('parse', () => { describe('wb', () => { describe('entities', () => { it('should be a function', () => { - parse.entities.should.be.a.Function() + should(parse.entities).be.a.Function() }) it('should parse an entities response', () => { const entities = parse.entities(wbgetentitiesResponse) - entities.should.be.an.Object() - entities.Q3235026.should.be.an.Object() - // @ts-expect-error - entities.Q3235026.labels.should.be.an.Object() - // @ts-expect-error - entities.Q3235026.descriptions.should.be.an.Object() - // @ts-expect-error - entities.Q3235026.claims.should.be.an.Object() + should(entities).be.an.Object() + if (entities.Q3235026.type !== 'item') throw new Error('should be an item') + should(entities.Q3235026).be.an.Object() + should(entities.Q3235026.labels).be.an.Object() + should(entities.Q3235026.descriptions).be.an.Object() + should(entities.Q3235026.claims).be.an.Object() }) }) describe('pagesTitles', () => { it('should parse a cirrus search response', () => { const titles = parse.pagesTitles(cirrusSearchPagesResponse) - titles.should.deepEqual([ 'Q1' ]) + should(titles).deepEqual([ 'Q1' ]) }) }) }) diff --git a/tests/rank.ts b/tests/rank.ts index 3c7fc43d..75741166 100644 --- a/tests/rank.ts +++ b/tests/rank.ts @@ -1,28 +1,29 @@ -import 'should' import { cloneDeep } from 'lodash-es' +import should from 'should' import { truthyClaims, truthyPropertyClaims } from '../src/helpers/rank.js' import { readJsonFile } from './lib/utils.js' +import type { Item } from '../src/types/entity.js' -const Q4115189 = readJsonFile('./tests/data/Q4115189.json') +const Q4115189 = readJsonFile('./tests/data/Q4115189.json') as Item describe('truthyClaims', () => { it('should filter-out non-truthy claims', () => { const Q4115189Claims = cloneDeep(Q4115189.claims) - Q4115189Claims.P135.length.should.equal(3) + should(Q4115189Claims.P135.length).equal(3) const truthyOnly = truthyClaims(Q4115189Claims) - truthyOnly.P135.length.should.equal(1) + should(truthyOnly.P135.length).equal(1) // @ts-expect-error - truthyOnly.P135[0].mainsnak.datavalue.value.id.should.equal('Q2044250') + should(truthyOnly.P135[0].mainsnak.datavalue.value.id).equal('Q2044250') }) }) describe('truthyPropertyClaims', () => { it('should filter-out non-truthy property claims', () => { const Q4115189Claims = cloneDeep(Q4115189.claims) - Q4115189Claims.P135.length.should.equal(3) + should(Q4115189Claims.P135.length).equal(3) const truthyOnly = truthyPropertyClaims(Q4115189Claims.P135) - truthyOnly.length.should.equal(1) + should(truthyOnly.length).equal(1) // @ts-expect-error - truthyOnly[0].mainsnak.datavalue.value.id.should.equal('Q2044250') + should(truthyOnly[0].mainsnak.datavalue.value.id).equal('Q2044250') }) }) diff --git a/tests/search_entities.ts b/tests/search_entities.ts index 2f920985..ccc42e71 100644 --- a/tests/search_entities.ts +++ b/tests/search_entities.ts @@ -1,4 +1,4 @@ -import 'should' +import should from 'should' import { searchEntitiesFactory } from '../src/queries/search_entities.js' import { buildUrl } from './lib/tests_env.js' import { parseUrlQuery } from './lib/utils.js' @@ -9,29 +9,29 @@ describe('searchEntities', () => { describe('action', () => { it('action should be wbsearchentities', () => { const query = parseUrlQuery(searchEntities({ search: 'Ingmar Bergman' })) - query.action.should.equal('wbsearchentities') + should(query.action).equal('wbsearchentities') }) }) describe('search', () => { it('accepts a string', () => { const query = parseUrlQuery(searchEntities({ search: 'johnnybegood' })) - query.search.should.equal('johnnybegood') + should(query.search).equal('johnnybegood') }) it('accepts an object', () => { const query = parseUrlQuery(searchEntities({ search: 'johnnybegood', language: 'fr' })) - query.search.should.equal('johnnybegood') - query.language.should.equal('fr') + should(query.search).equal('johnnybegood') + should(query.language).equal('fr') }) it('throw on empty string', () => { - (() => searchEntities({ search: '' })).should.throw() + should(() => (searchEntities({ search: '' }))).throw() }) it('should default to continue=0', () => { const query = parseUrlQuery(searchEntities({ search: 'Ingmar Bergman' })) - query.continue.should.equal('0') + should(query.continue).equal('0') }) }) @@ -40,7 +40,7 @@ describe('searchEntities', () => { const query = parseUrlQuery(searchEntities({ search: 'Ingmar Bergman', })) - query.language.should.equal('en') + should(query.language).equal('en') }) it('should accept a string', () => { @@ -48,7 +48,7 @@ describe('searchEntities', () => { search: 'Ingmar Bergman', language: 'la', })) - query.language.should.equal('la') + should(query.language).equal('la') }) it('should set uselang as language by default', () => { @@ -56,7 +56,7 @@ describe('searchEntities', () => { search: 'Ingmar Bergman', language: 'la', })) - query.uselang.should.equal('la') + should(query.uselang).equal('la') }) it('should accept a uselang parameter different from language', () => { @@ -66,48 +66,48 @@ describe('searchEntities', () => { language: 'la', uselang: 'eo', })) - query.language.should.equal('la') - query.uselang.should.equal('eo') + should(query.language).equal('la') + should(query.uselang).equal('eo') }) }) describe('format', () => { it('should have json as default format', () => { const query = parseUrlQuery(searchEntities({ search: 'Ingmar Bergman' })) - query.format.should.equal('json') + should(query.format).equal('json') }) }) describe('encoding', () => { it('should url encode the query', () => { const url = searchEntities({ search: 'C#' }) - url.should.containEql('C%23') + should(url).containEql('C%23') }) }) describe('type', () => { it('should accept a valid type parameter', () => { const query = parseUrlQuery(searchEntities({ search: 'alphabet', type: 'property' })) - query.type.should.equal('property') + should(query.type).equal('property') }) it('should reject an invalid type parameter', () => { // @ts-expect-error - (() => searchEntities({ search: 'alphabet', type: 'foo' })).should.throw() + should(() => (searchEntities({ search: 'alphabet', type: 'foo' }))).throw() }) }) describe('limit', () => { it('should reject an invalid type parameter', () => { const query = parseUrlQuery(searchEntities({ search: 'alphabet', limit: 10 })) - query.limit.should.equal('10') + should(query.limit).equal('10') }) }) describe('continue', () => { it('should reject an invalid type parameter', () => { const query = parseUrlQuery(searchEntities({ search: 'alphabet', continue: 10 })) - query.continue.should.equal('10') + should(query.continue).equal('10') }) }) }) diff --git a/tests/simplify_claims.ts b/tests/simplify_claims.ts index 857bd5af..c974aa28 100644 --- a/tests/simplify_claims.ts +++ b/tests/simplify_claims.ts @@ -1,72 +1,72 @@ // @ts-nocheck -import { every } from 'lodash-es' import should from 'should' import { simplifyClaim, simplifyPropertyClaims, simplifyClaims } from '../src/helpers/simplify_claims.js' import { uniq } from '../src/utils/utils.js' import { readJsonFile } from './lib/utils.js' +import type { Item, Lexeme } from '../src/types/entity.js' import type { SimplifySnakOptions } from '../src/types/simplify_claims.js' -const L525 = readJsonFile('./tests/data/L525.json') -const Q1 = readJsonFile('./tests/data/Q1.json') -const Q2112 = readJsonFile('./tests/data/Q2112.json') -const Q217447 = readJsonFile('./tests/data/Q217447.json') -const Q22002395 = readJsonFile('./tests/data/Q22002395.json') -const Q271094 = readJsonFile('./tests/data/Q271094.json') -const Q275937 = readJsonFile('./tests/data/Q275937.json') -const Q328212 = readJsonFile('./tests/data/Q328212.json') -const Q4115189 = readJsonFile('./tests/data/Q4115189.json') -const Q4132785 = readJsonFile('./tests/data/Q4132785.json') -const Q571 = readJsonFile('./tests/data/Q571.json') -const Q646148 = readJsonFile('./tests/data/Q646148.json') +const L525 = readJsonFile('./tests/data/L525.json') as Lexeme +const Q1 = readJsonFile('./tests/data/Q1.json') as Item +const Q2112 = readJsonFile('./tests/data/Q2112.json') as Item +const Q217447 = readJsonFile('./tests/data/Q217447.json') as Item +const Q22002395 = readJsonFile('./tests/data/Q22002395.json') as Item +const Q271094 = readJsonFile('./tests/data/Q271094.json') as Item +const Q275937 = readJsonFile('./tests/data/Q275937.json') as Item +const Q328212 = readJsonFile('./tests/data/Q328212.json') as Item +const Q4115189 = readJsonFile('./tests/data/Q4115189.json') as Item +const Q4132785 = readJsonFile('./tests/data/Q4132785.json') as Item +const Q571 = readJsonFile('./tests/data/Q571.json') as Item +const Q646148 = readJsonFile('./tests/data/Q646148.json') as Item const emptyValues = readJsonFile('./tests/data/empty_values.json') const lexemeClaim = readJsonFile('./tests/data/lexeme_claim.json') const oldClaimFormat = readJsonFile('./tests/data/old_claim_format.json') describe('simplifyClaims', () => { it('env', () => { - Q571.should.be.an.Object() - Q571.claims.should.be.ok() - Q4132785.should.be.an.Object() - Q4132785.claims.P577[0].should.be.ok() + should(Q571).be.an.Object() + should(Q571.claims).be.ok() + should(Q4132785).be.an.Object() + should(Q4132785.claims.P577[0]).be.ok() }) it('should return an object', () => { - simplifyClaims(Q571.claims).should.be.an.Object() + should(simplifyClaims(Q571.claims)).be.an.Object() }) it('should not mutate the original object', () => { const simplified = simplifyClaims(Q571.claims) - simplified.should.not.equal(Q571.claims) - simplified.P487.should.not.equal(Q571.claims.P487) + should(simplified).not.equal(Q571.claims) + should(simplified.P487).not.equal(Q571.claims.P487) }) it('should return an object of same length', () => { const originalLength = Object.keys(Q571.claims).length const simplified = simplifyClaims(Q571.claims) const newLength = Object.keys(simplified).length - newLength.should.equal(originalLength) + should(newLength).equal(originalLength) }) it('should return an indexed collection of arrays', () => { const simplified = simplifyClaims(Q571.claims) for (const key in simplified) { - simplified[key].should.be.an.Array() + should(simplified[key]).be.an.Array() } }) it('should pass entity and property prefixes down', () => { const simplified = simplifyClaims(Q2112.claims, { entityPrefix: 'wd' }) - simplified.P190[0].should.equal('wd:Q207614') + should(simplified.P190[0]).equal('wd:Q207614') const simplified2 = simplifyClaims(Q2112.claims, { propertyPrefix: 'wdt' }) - simplified2['wdt:P123456789'][0].should.equal('P207614') + should(simplified2['wdt:P123456789'][0]).equal('P207614') }) it('should return prefixed properties if passed a property prefix', () => { const simplified = simplifyClaims(Q2112.claims, { entityPrefix: 'wd', propertyPrefix: 'wdt' }) - simplified['wdt:P190'].should.be.an.Array() - simplified['wdt:P190'][0].should.equal('wd:Q207614') + should(simplified['wdt:P190']).be.an.Array() + should(simplified['wdt:P190'][0]).equal('wd:Q207614') const simplified2 = simplifyClaims(Q2112.claims, { propertyPrefix: 'wdt' }) - simplified2['wdt:P123456789'][0].should.equal('P207614') + should(simplified2['wdt:P123456789'][0]).equal('P207614') }) it('should return the correct value when called with keepQualifiers=true', () => { @@ -74,9 +74,9 @@ describe('simplifyClaims', () => { const simplifiedWithQualifiers = simplifyClaims(Q571.claims, { keepQualifiers: true }) Object.keys(simplifiedWithQualifiers).forEach(property => { const propertyValues = simplifiedWithQualifiers[property] - propertyValues.should.be.an.Array() + should(propertyValues).be.an.Array() propertyValues.forEach((valueObj, index) => { - valueObj.should.be.an.Object() + should(valueObj).be.an.Object() const value = simplified[property][index] should(valueObj.value).equal(value) should(valueObj.qualifiers).be.an.Object() @@ -95,42 +95,42 @@ describe('simplifyClaims', () => { describe('simplifyPropertyClaims', () => { it('should return an arrays', () => { const simplified = simplifyPropertyClaims(Q571.claims.P487) - simplified.should.be.an.Array() + should(simplified).be.an.Array() }) it('should not mutate the original array', () => { const simplified = simplifyPropertyClaims(Q571.claims.P487) - simplified.should.not.equal(Q571.claims.P487) - simplified[0].should.not.equal(Q571.claims.P487[0]) + should(simplified).not.equal(Q571.claims.P487) + should(simplified[0]).not.equal(Q571.claims.P487[0]) }) it('should keep only non-null values', () => { const simplified = simplifyPropertyClaims(Q22002395.claims.P50) // Q22002395 P50 has 2 values with "snaktype": "somevalue" // that should be removed - every(simplified, qid => qid != null).should.equal(true) + simplified.forEach(qid => should(qid != null).be.true()) }) it('should deduplicated values', () => { const { P50 } = Q22002395.claims const claimsWithDuplicates = P50.concat(P50) const simplified = simplifyPropertyClaims(claimsWithDuplicates) - uniq(simplified).length.should.equal(simplified.length) + should(uniq(simplified).length).equal(simplified.length) }) it('should pass entity and property prefixes down', () => { const simplified = simplifyPropertyClaims(Q2112.claims.P190, { entityPrefix: 'wd' }) - simplified[0].should.equal('wd:Q207614') + should(simplified[0]).equal('wd:Q207614') const simplified2 = simplifyPropertyClaims(Q2112.claims.P123456789, { entityPrefix: 'a', propertyPrefix: 'b' }) - simplified2[0].should.equal('a:P207614') + should(simplified2[0]).equal('a:P207614') }) it('should return the correct value when called with keepQualifiers=true', () => { const simplified = simplifyPropertyClaims(Q571.claims.P279) const simplifiedWithQualifiers = simplifyPropertyClaims(Q571.claims.P279, { keepQualifiers: true }) - simplifiedWithQualifiers.should.be.an.Array() + should(simplifiedWithQualifiers).be.an.Array() simplifiedWithQualifiers.forEach((valueObj, index) => { - valueObj.should.be.an.Object() + should(valueObj).be.an.Object() const value = simplified[index] should(valueObj.value).equal(value) should(valueObj.qualifiers).be.an.Object() @@ -140,54 +140,52 @@ describe('simplifyPropertyClaims', () => { it('should include prefixes in qualifiers claims', () => { const simplifiedWithQualifiers = simplifyPropertyClaims(Q646148.claims.P39, { entityPrefix: 'wd', propertyPrefix: 'wdt', keepQualifiers: true }) const propertyQualifiers = simplifiedWithQualifiers[1].qualifiers['wdt:P1365'] - propertyQualifiers.should.be.an.Array() - propertyQualifiers[0].should.equal('wd:Q312881') + should(propertyQualifiers).be.an.Array() + should(propertyQualifiers[0]).equal('wd:Q312881') }) it('construct entity ids for old dump format', () => { const simplified = simplifyPropertyClaims(oldClaimFormat) - simplified.length.should.equal(2) - simplified[0].should.equal('Q123') - simplified[1].should.equal('P123') + should(simplified.length).equal(2) + should(simplified[0]).equal('Q123') + should(simplified[1]).equal('P123') }) it('should tolerate empty inputs', () => { // @ts-expect-error empty input is not typed const simplified = simplifyPropertyClaims() - simplified.should.be.an.Array() - simplified.length.should.equal(0) + should(simplified).be.an.Array() + should(simplified.length).equal(0) const simplified2 = simplifyPropertyClaims([]) - simplified2.should.be.an.Array() - simplified2.length.should.equal(0) + should(simplified2).be.an.Array() + should(simplified2.length).equal(0) }) describe('ranks', () => { it('should return only truthy statements by default', () => { const simplified = simplifyPropertyClaims(Q4115189.claims.P135) - simplified.length.should.equal(1) - simplified.should.deepEqual([ 'Q2044250' ]) + should(simplified.length).equal(1) + should(simplified).deepEqual([ 'Q2044250' ]) }) it('should return non-truthy statements if requested', () => { const options = { keepNonTruthy: true } const simplified = simplifyPropertyClaims(Q4115189.claims.P135, options) - simplified.should.deepEqual([ 'Q213454', 'Q2044250', 'Q5843' ]) + should(simplified).deepEqual([ 'Q213454', 'Q2044250', 'Q5843' ]) }) it('should return non-deprecated statements if requested', () => { const options = { keepNonDeprecated: true } const simplified = simplifyPropertyClaims(Q4115189.claims.P135, options) - simplified.length.should.equal(2) - simplified.should.deepEqual([ 'Q2044250', 'Q5843' ]) + should(simplified.length).equal(2) + should(simplified).deepEqual([ 'Q2044250', 'Q5843' ]) }) it('should keep ranks', () => { - simplifyPropertyClaims(Q4115189.claims.P135, { keepRanks: true }) - .should.deepEqual([ + should(simplifyPropertyClaims(Q4115189.claims.P135, { keepRanks: true })).deepEqual([ { value: 'Q2044250', rank: 'preferred' }, ]) - simplifyPropertyClaims(Q4115189.claims.P135, { keepRanks: true, keepNonTruthy: true }) - .should.deepEqual([ + should(simplifyPropertyClaims(Q4115189.claims.P135, { keepRanks: true, keepNonTruthy: true })).deepEqual([ { value: 'Q213454', rank: 'deprecated' }, { value: 'Q2044250', rank: 'preferred' }, { value: 'Q5843', rank: 'normal' }, @@ -197,18 +195,18 @@ describe('simplifyPropertyClaims', () => { describe('empty values', () => { it('should not filter-out empty values if given a placeholder value', () => { - simplifyPropertyClaims(emptyValues.claims.P3984).length.should.equal(1) - simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: '-' }).length.should.equal(2) - simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: null }).length.should.equal(2) - simplifyPropertyClaims(emptyValues.claims.P3984, { somevalueValue: '?' }).length.should.equal(2) - simplifyPropertyClaims(emptyValues.claims.P3984, { somevalueValue: null }).length.should.equal(2) - simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: null, somevalueValue: null }).length.should.equal(3) - simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: '-', somevalueValue: '?' }).length.should.equal(3) - simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: '-', somevalueValue: '?' }).should.deepEqual([ '-', '?', 'bacasable' ]) + should(simplifyPropertyClaims(emptyValues.claims.P3984).length).equal(1) + should(simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: '-' }).length).equal(2) + should(simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: null }).length).equal(2) + should(simplifyPropertyClaims(emptyValues.claims.P3984, { somevalueValue: '?' }).length).equal(2) + should(simplifyPropertyClaims(emptyValues.claims.P3984, { somevalueValue: null }).length).equal(2) + should(simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: null, somevalueValue: null }).length).equal(3) + should(simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: '-', somevalueValue: '?' }).length).equal(3) + should(simplifyPropertyClaims(emptyValues.claims.P3984, { novalueValue: '-', somevalueValue: '?' })).deepEqual([ '-', '?', 'bacasable' ]) }) it('should keep snaktype if requested', () => { - simplifyPropertyClaims(emptyValues.claims.P3984, { keepSnaktypes: true }).should.deepEqual([ + should(simplifyPropertyClaims(emptyValues.claims.P3984, { keepSnaktypes: true })).deepEqual([ { value: undefined, snaktype: 'novalue' }, { value: undefined, snaktype: 'somevalue' }, { value: 'bacasable', snaktype: 'value' }, @@ -217,8 +215,7 @@ describe('simplifyPropertyClaims', () => { keepSnaktypes: true, novalueValue: '-', somevalueValue: '?', - }) - .should.deepEqual([ + }).should.deepEqual([ { value: '-', snaktype: 'novalue' }, { value: '?', snaktype: 'somevalue' }, { value: 'bacasable', snaktype: 'value' }, @@ -226,22 +223,22 @@ describe('simplifyPropertyClaims', () => { }) it('should not filter-out empty values if requested as object values', () => { - simplifyPropertyClaims(emptyValues.claims.P3984, { keepQualifiers: true }).should.deepEqual([ + should(simplifyPropertyClaims(emptyValues.claims.P3984, { keepQualifiers: true })).deepEqual([ { value: undefined, qualifiers: {} }, { value: undefined, qualifiers: {} }, { value: 'bacasable', qualifiers: {} }, ]) - simplifyPropertyClaims(emptyValues.claims.P3984, { keepReferences: true }).should.deepEqual([ + should(simplifyPropertyClaims(emptyValues.claims.P3984, { keepReferences: true })).deepEqual([ { value: undefined, references: [] }, { value: undefined, references: [] }, { value: 'bacasable', references: [] }, ]) - simplifyPropertyClaims(emptyValues.claims.P3984, { keepIds: true }).should.deepEqual([ + should(simplifyPropertyClaims(emptyValues.claims.P3984, { keepIds: true })).deepEqual([ { value: undefined, id: 'Q4115189$c973aadc-48d3-5ac2-45fc-9f34a51ebdf6' }, { value: undefined, id: 'Q4115189$db1940f1-41bd-ad24-8fbc-20bc6465a35f' }, { value: 'bacasable', id: 'Q4115189$5c85ec5e-48f5-716d-8944-c4364693e406' }, ]) - simplifyPropertyClaims(emptyValues.claims.P3984, { keepTypes: true }).should.deepEqual([ + should(simplifyPropertyClaims(emptyValues.claims.P3984, { keepTypes: true })).deepEqual([ { value: undefined, type: 'external-id' }, { value: undefined, type: 'external-id' }, { value: 'bacasable', type: 'external-id' }, @@ -254,8 +251,7 @@ describe('simplifyPropertyClaims', () => { keepQualifiers: true, novalueValue: '-', somevalueValue: '?', - }) - .should.deepEqual([ + }).should.deepEqual([ { value: '-', qualifiers: {} }, { value: '?', qualifiers: {} }, { value: 'bacasable', qualifiers: {} }, @@ -267,66 +263,66 @@ describe('simplifyClaim', () => { describe('datatypes', () => { it('should return a url for datatype url', () => { const simplified = simplifyClaim(Q328212.claims.P856[0]) - simplified.should.equal('http://veronicarothbooks.blogspot.com') + should(simplified).equal('http://veronicarothbooks.blogspot.com') }) it('should return simplified globecoordinate as a latLng array', () => { const simplified = simplifyClaim(Q2112.claims.P625[0]) - simplified.should.be.an.Array() - simplified[0].should.equal(52.016666666667) - simplified[1].should.equal(8.5166666666667) + should(simplified).be.an.Array() + should(simplified[0]).equal(52.016666666667) + should(simplified[1]).equal(8.5166666666667) }) it('should support geo-shape', () => { - simplifyClaim(Q217447.claims.P3896[0]).should.equal('Data:Rky/1277_Verlan_teollisuusympäristö.map') + should(simplifyClaim(Q217447.claims.P3896[0])).equal('Data:Rky/1277_Verlan_teollisuusympäristö.map') }) it('should support tabular-data', () => { - simplifyClaim(Q271094.claims.P4179[0]).should.equal('Data:Taipei Neihu District Population.tab') + should(simplifyClaim(Q271094.claims.P4179[0])).equal('Data:Taipei Neihu District Population.tab') }) it('should support lexemes', () => { - simplifyClaim(lexemeClaim).should.equal('L397') + should(simplifyClaim(lexemeClaim)).equal('L397') }) it('should support musical-notation', () => { - simplifyClaim(Q4115189.claims.P6604[0]).should.equal('\\relative { c d e f g e }') + should(simplifyClaim(Q4115189.claims.P6604[0])).equal('\\relative { c d e f g e }') }) it('should support wikibase-form', () => { - simplifyClaim(Q275937.claims.P8017[0]).should.equal('L252247-F2') + should(simplifyClaim(Q275937.claims.P8017[0])).equal('L252247-F2') }) it('should support wikibase-sense', () => { - simplifyClaim(L525.claims.P5972[0]).should.equal('L512-S1') + should(simplifyClaim(L525.claims.P5972[0])).equal('L512-S1') }) }) describe('prefixes', () => { it('should return prefixed entity ids if passed an entity prefix', () => { const claim = Q2112.claims.P190[0] - simplifyClaim(claim).should.equal('Q207614') - simplifyClaim(claim, { entityPrefix: 'wd' }).should.equal('wd:Q207614') - simplifyClaim(claim, { entityPrefix: 'wd:' }).should.equal('wd::Q207614') - simplifyClaim(claim, { entityPrefix: 'wdbla' }).should.equal('wdbla:Q207614') + should(simplifyClaim(claim)).equal('Q207614') + should(simplifyClaim(claim, { entityPrefix: 'wd' })).equal('wd:Q207614') + should(simplifyClaim(claim, { entityPrefix: 'wd:' })).equal('wd::Q207614') + should(simplifyClaim(claim, { entityPrefix: 'wdbla' })).equal('wdbla:Q207614') }) it('should not apply property prefixes to property claim values', () => { const claim = Q2112.claims.P123456789[0] - simplifyClaim(claim).should.equal('P207614') - simplifyClaim(claim, { propertyPrefix: 'wdt' }).should.equal('P207614') - simplifyClaim(claim, { propertyPrefix: 'wdt:' }).should.equal('P207614') - simplifyClaim(claim, { propertyPrefix: 'wdtbla' }).should.equal('P207614') - simplifyClaim(claim, { entityPrefix: 'wd' }).should.equal('wd:P207614') - simplifyClaim(claim, { entityPrefix: 'wd:' }).should.equal('wd::P207614') - simplifyClaim(claim, { entityPrefix: 'wdbla' }).should.equal('wdbla:P207614') + should(simplifyClaim(claim)).equal('P207614') + should(simplifyClaim(claim, { propertyPrefix: 'wdt' })).equal('P207614') + should(simplifyClaim(claim, { propertyPrefix: 'wdt:' })).equal('P207614') + should(simplifyClaim(claim, { propertyPrefix: 'wdtbla' })).equal('P207614') + should(simplifyClaim(claim, { entityPrefix: 'wd' })).equal('wd:P207614') + should(simplifyClaim(claim, { entityPrefix: 'wd:' })).equal('wd::P207614') + should(simplifyClaim(claim, { entityPrefix: 'wdbla' })).equal('wdbla:P207614') }) }) describe('keepTypes', () => { it('should return the correct value when called with keepQualifiers=true', () => { const simplified = simplifyClaim(Q2112.claims.P190[0], { keepTypes: true }) - simplified.should.deepEqual({ value: 'Q207614', type: 'wikibase-item' }) + should(simplified).deepEqual({ value: 'Q207614', type: 'wikibase-item' }) }) }) @@ -341,51 +337,51 @@ describe('simplifyClaim', () => { it('should include qualifiers when called with keepQualifiers=true', () => { const simplifiedWithQualifiers = simplifyClaim(Q571.claims.P1709[0], { keepQualifiers: true }) const { P973, P813 } = simplifiedWithQualifiers.qualifiers - P973.should.be.an.Array() - P973[0].should.equal('http://mappings.dbpedia.org/index.php/OntologyClass:Book') - P813.should.be.an.Array() - P813[0].should.equal('2015-06-11T00:00:00.000Z') + should(P973).be.an.Array() + should(P973[0]).equal('http://mappings.dbpedia.org/index.php/OntologyClass:Book') + should(P813).be.an.Array() + should(P813[0]).equal('2015-06-11T00:00:00.000Z') }) it('should include prefixes in qualifiers claims', () => { const simplifiedWithQualifiers = simplifyClaim(Q646148.claims.P39[1], { entityPrefix: 'wd', propertyPrefix: 'wdt', keepQualifiers: true }) const propertyQualifiers = simplifiedWithQualifiers.qualifiers['wdt:P1365'] - propertyQualifiers.should.be.an.Array() - propertyQualifiers[0].should.equal('wd:Q312881') + should(propertyQualifiers).be.an.Array() + should(propertyQualifiers[0]).equal('wd:Q312881') }) it('should include types in qualifiers claims', () => { const simplifiedWithQualifiers = simplifyClaim(Q646148.claims.P39[1], { keepTypes: true, keepQualifiers: true }) const { P1365 } = simplifiedWithQualifiers.qualifiers - P1365.should.be.an.Array() - P1365[0].should.deepEqual({ value: 'Q312881', type: 'wikibase-item' }) + should(P1365).be.an.Array() + should(P1365[0]).deepEqual({ value: 'Q312881', type: 'wikibase-item' }) }) it('should respect timeConverter for qualifiers claims', () => { let simplifiedWithQualifiers = simplifyClaim(Q571.claims.P1709[0], { keepQualifiers: true, timeConverter: 'iso' }) let P813 = simplifiedWithQualifiers.qualifiers.P813 - P813.should.be.an.Array() - P813[0].should.equal('2015-06-11T00:00:00.000Z') + should(P813).be.an.Array() + should(P813[0]).equal('2015-06-11T00:00:00.000Z') simplifiedWithQualifiers = simplifyClaim(Q571.claims.P1709[0], { keepQualifiers: true, timeConverter: 'epoch' }) P813 = simplifiedWithQualifiers.qualifiers.P813 - P813.should.be.an.Array() - P813[0].should.equal(1433980800000) + should(P813).be.an.Array() + should(P813[0]).equal(1433980800000) simplifiedWithQualifiers = simplifyClaim(Q571.claims.P1709[0], { keepQualifiers: true, timeConverter: 'simple-day' }) P813 = simplifiedWithQualifiers.qualifiers.P813 - P813.should.be.an.Array() - P813[0].should.equal('2015-06-11') + should(P813).be.an.Array() + should(P813[0]).equal('2015-06-11') simplifiedWithQualifiers = simplifyClaim(Q571.claims.P1709[0], { keepQualifiers: true, timeConverter: 'none' }) P813 = simplifiedWithQualifiers.qualifiers.P813 - P813.should.be.an.Array() - P813[0].should.equal('+2015-06-11T00:00:00Z') + should(P813).be.an.Array() + should(P813[0]).equal('+2015-06-11T00:00:00Z') simplifiedWithQualifiers = simplifyClaim(Q571.claims.P1709[0], { keepQualifiers: true, timeConverter: v => `foo/${v.time}/${v.precision}/bar` }) P813 = simplifiedWithQualifiers.qualifiers.P813 - P813.should.be.an.Array() - P813[0].should.equal('foo/+2015-06-11T00:00:00Z/11/bar') + should(P813).be.an.Array() + should(P813[0]).equal('foo/+2015-06-11T00:00:00Z/11/bar') }) }) @@ -393,22 +389,22 @@ describe('simplifyClaim', () => { it('should return the correct value when called with keepReferences=true', () => { const simplified = simplifyClaim(Q2112.claims.P214[0]) const simplifiedWithReferences = simplifyClaim(Q2112.claims.P214[0], { keepReferences: true }) - simplifiedWithReferences.value.should.equal(simplified) - simplifiedWithReferences.references.should.be.an.Object() + should(simplifiedWithReferences.value).equal(simplified) + should(simplifiedWithReferences.references).be.an.Object() }) it('should include references when called with keepReferences=true', () => { const simplifiedWithReferences = simplifyClaim(Q2112.claims.P214[0], { keepReferences: true }) - simplifiedWithReferences.references[0].P248.should.be.an.Array() - simplifiedWithReferences.references[0].P248[0].should.equal('Q54919') - simplifiedWithReferences.references[0].P813.should.be.an.Array() - simplifiedWithReferences.references[0].P813[0].should.equal('2015-08-02T00:00:00.000Z') + should(simplifiedWithReferences.references[0].P248).be.an.Array() + should(simplifiedWithReferences.references[0].P248[0]).equal('Q54919') + should(simplifiedWithReferences.references[0].P813).be.an.Array() + should(simplifiedWithReferences.references[0].P813[0]).equal('2015-08-02T00:00:00.000Z') }) it('should include prefixes in references claims', () => { const simplifiedWithReferences = simplifyClaim(Q2112.claims.P214[0], { entityPrefix: 'wd', propertyPrefix: 'wdt', keepReferences: true }) - simplifiedWithReferences.references[0]['wdt:P248'].should.be.an.Array() - simplifiedWithReferences.references[0]['wdt:P248'][0].should.equal('wd:Q54919') + should(simplifiedWithReferences.references[0]['wdt:P248']).be.an.Array() + should(simplifiedWithReferences.references[0]['wdt:P248'][0]).equal('wd:Q54919') }) }) @@ -416,13 +412,13 @@ describe('simplifyClaim', () => { it('should return the correct value when called with keepIds=true', () => { const simplified = simplifyClaim(Q2112.claims.P214[0]) const simplifiedWithIds = simplifyClaim(Q2112.claims.P214[0], { keepIds: true }) - simplifiedWithIds.value.should.equal(simplified) - simplifiedWithIds.id.should.be.a.String() + should(simplifiedWithIds.value).equal(simplified) + should(simplifiedWithIds.id).be.a.String() }) it('should include ids when called with keepReferences=true', () => { const simplifiedWithIds = simplifyClaim(Q2112.claims.P214[0], { keepIds: true }) - simplifiedWithIds.id.should.equal('Q2112$ECB9E5BB-B2E1-4E77-8CEE-4E9F4938EB86') + should(simplifiedWithIds.id).equal('Q2112$ECB9E5BB-B2E1-4E77-8CEE-4E9F4938EB86') }) }) @@ -430,42 +426,42 @@ describe('simplifyClaim', () => { it('should return the correct value when called with keepHashes=true', () => { const simplified = simplifyClaim(Q2112.claims.P214[0]) const simplifiedWithReferences = simplifyClaim(Q2112.claims.P214[0], { keepReferences: true, keepQualifiers: true, keepHashes: true }) - simplifiedWithReferences.value.should.equal(simplified) + should(simplifiedWithReferences.value).equal(simplified) }) it('should include references hashes when called with keepHashes=true', () => { const simplifiedWithReferences = simplifyClaim(Q2112.claims.P214[0], { keepReferences: true, keepHashes: true }) - simplifiedWithReferences.references[0].snaks.P248.should.be.an.Array() - simplifiedWithReferences.references[0].hash.should.equal('d6b4bc80e47def2fab91836d81e1db62c640279c') - simplifiedWithReferences.references[0].snaks.P248[0].should.equal('Q54919') - simplifiedWithReferences.references[0].snaks.P813.should.be.an.Array() - simplifiedWithReferences.references[0].snaks.P813[0].should.equal('2015-08-02T00:00:00.000Z') + should(simplifiedWithReferences.references[0].snaks.P248).be.an.Array() + should(simplifiedWithReferences.references[0].hash).equal('d6b4bc80e47def2fab91836d81e1db62c640279c') + should(simplifiedWithReferences.references[0].snaks.P248[0]).equal('Q54919') + should(simplifiedWithReferences.references[0].snaks.P813).be.an.Array() + should(simplifiedWithReferences.references[0].snaks.P813[0]).equal('2015-08-02T00:00:00.000Z') }) it('should include qualifiers hashes when called with keepHashes=true', () => { const simplifiedWithQualifiers = simplifyPropertyClaims(Q2112.claims.P190, { keepQualifiers: true, keepHashes: true }) - simplifiedWithQualifiers[1].qualifiers.P580[0].value.should.equal('1953-01-01T00:00:00.000Z') - simplifiedWithQualifiers[1].qualifiers.P580[0].hash.should.equal('3d22f4dffba1ac6f66f521ea6bea924e46df4129') + should(simplifiedWithQualifiers[1].qualifiers.P580[0].value).equal('1953-01-01T00:00:00.000Z') + should(simplifiedWithQualifiers[1].qualifiers.P580[0].hash).equal('3d22f4dffba1ac6f66f521ea6bea924e46df4129') }) }) describe('rich values', () => { it('should keep monolingual rich values', () => { const simplified = simplifyClaim(Q328212.claims.P1477[0], { keepRichValues: true }) - simplified.text.should.equal('Veronica Roth') - simplified.language.should.equal('es') + should(simplified.text).equal('Veronica Roth') + should(simplified.language).equal('es') }) it('should keep quantity rich values', () => { const simplified = simplifyClaim(Q2112.claims.P2044[0], { keepRichValues: true }) - simplified.amount.should.equal(118) - simplified.unit.should.equal('Q11573') - simplified.upperBound.should.equal(119) - simplified.lowerBound.should.equal(117) + should(simplified.amount).equal(118) + should(simplified.unit).equal('Q11573') + should(simplified.upperBound).equal(119) + should(simplified.lowerBound).equal(117) }) it('should keep globecoordinate rich values', () => { - simplifyClaim(Q2112.claims.P625[0], { keepRichValues: true }).should.deepEqual({ + should(simplifyClaim(Q2112.claims.P625[0], { keepRichValues: true })).deepEqual({ latitude: 52.016666666667, longitude: 8.5166666666667, altitude: null, @@ -475,7 +471,7 @@ describe('simplifyClaim', () => { }) it('should keep time rich values', () => { - simplifyClaim(Q646148.claims.P569[0], { keepRichValues: true }).should.deepEqual({ + should(simplifyClaim(Q646148.claims.P569[0], { keepRichValues: true })).deepEqual({ time: '1939-11-08T00:00:00.000Z', timezone: 0, before: 0, @@ -490,27 +486,27 @@ describe('simplifyClaim', () => { it('should use a custom time converter when one is set', () => { const claim = Q646148.claims.P569[0] const simplifyTimeClaim = (timeConverter: SimplifySnakOptions['timeConverter']) => simplifyClaim(claim, { timeConverter }) - simplifyTimeClaim(undefined).should.equal('1939-11-08T00:00:00.000Z') - simplifyTimeClaim('iso').should.equal('1939-11-08T00:00:00.000Z') - simplifyTimeClaim('epoch').should.equal(-951436800000) - simplifyTimeClaim('simple-day').should.equal('1939-11-08') - simplifyTimeClaim('none').should.equal('+1939-11-08T00:00:00Z') + should(simplifyTimeClaim(undefined)).equal('1939-11-08T00:00:00.000Z') + should(simplifyTimeClaim('iso')).equal('1939-11-08T00:00:00.000Z') + should(simplifyTimeClaim('epoch')).equal(-951436800000) + should(simplifyTimeClaim('simple-day')).equal('1939-11-08') + should(simplifyTimeClaim('none')).equal('+1939-11-08T00:00:00Z') const timeConverterFn = ({ time, precision }) => `foo/${time}/${precision}/bar` - simplifyTimeClaim(timeConverterFn).should.equal('foo/+1939-11-08T00:00:00Z/11/bar') + should(simplifyTimeClaim(timeConverterFn)).equal('foo/+1939-11-08T00:00:00Z/11/bar') }) it('should be able to parse long dates', () => { const claim = Q1.claims.P580[0] const simplifyTimeClaim = (timeConverter: SimplifySnakOptions['timeConverter']) => simplifyClaim(claim, { timeConverter }) - simplifyTimeClaim(undefined).should.equal('-13798000000-01-01T00:00:00Z') - simplifyTimeClaim('none').should.equal('-13798000000-00-00T00:00:00Z') - simplifyTimeClaim('iso').should.equal('-13798000000-01-01T00:00:00Z') - simplifyTimeClaim('simple-day').should.equal('-13798000000') + should(simplifyTimeClaim(undefined)).equal('-13798000000-01-01T00:00:00Z') + should(simplifyTimeClaim('none')).equal('-13798000000-00-00T00:00:00Z') + should(simplifyTimeClaim('iso')).equal('-13798000000-01-01T00:00:00Z') + should(simplifyTimeClaim('simple-day')).equal('-13798000000') const timeConverterFn = ({ time, precision }) => `foo/${time}/${precision}/bar` - simplifyTimeClaim(timeConverterFn).should.equal('foo/-13798000000-00-00T00:00:00Z/3/bar') + should(simplifyTimeClaim(timeConverterFn)).equal('foo/-13798000000-00-00T00:00:00Z/3/bar') // Can't be supported due to JS large numbers limitations; // 13798000000*365.25*24*60*60*1000 is too big - // timeClaim('epoch').should.equal('-13798000000-00-00T00:00:00Z') + // should(timeClaim('epoch')).equal('-13798000000-00-00T00:00:00Z') }) }) @@ -518,15 +514,15 @@ describe('simplifyClaim', () => { it('should return the desired novalueValue', () => { const noValueClaim = emptyValues.claims.P3984[0] should(simplifyClaim(noValueClaim)).not.be.ok() - simplifyClaim(noValueClaim, { novalueValue: '-' }).should.equal('-') - simplifyClaim(noValueClaim, { novalueValue: '' }).should.equal('') + should(simplifyClaim(noValueClaim, { novalueValue: '-' })).equal('-') + should(simplifyClaim(noValueClaim, { novalueValue: '' })).equal('') }) it('should return the desired somevalueValue', () => { const someValueClaim = emptyValues.claims.P3984[1] should(simplifyClaim(someValueClaim)).not.be.ok() - simplifyClaim(someValueClaim, { somevalueValue: '?' }).should.equal('?') - simplifyClaim(someValueClaim, { somevalueValue: '' }).should.equal('') + should(simplifyClaim(someValueClaim, { somevalueValue: '?' })).equal('?') + should(simplifyClaim(someValueClaim, { somevalueValue: '' })).equal('') }) it('should accept null as a possible value', () => { @@ -535,10 +531,10 @@ describe('simplifyClaim', () => { }) it('should return rich values for null values if requested', () => { - simplifyClaim(emptyValues.claims.P3984[0], { keepQualifiers: true }).should.have.property('qualifiers') - simplifyClaim(emptyValues.claims.P3984[0], { keepReferences: true }).should.have.property('references') - simplifyClaim(emptyValues.claims.P3984[0], { keepIds: true }).should.have.property('id') - simplifyClaim(emptyValues.claims.P3984[0], { keepTypes: true }).should.have.property('type') + should(simplifyClaim(emptyValues.claims.P3984[0], { keepQualifiers: true })).have.property('qualifiers') + should(simplifyClaim(emptyValues.claims.P3984[0], { keepReferences: true })).have.property('references') + should(simplifyClaim(emptyValues.claims.P3984[0], { keepIds: true })).have.property('id') + should(simplifyClaim(emptyValues.claims.P3984[0], { keepTypes: true })).have.property('type') }) }) @@ -546,14 +542,14 @@ describe('simplifyClaim', () => { it('should activate all keep options', () => { const simplifiedP214 = simplifyClaim(Q2112.claims.P214[0], { keepAll: true }) const simplifiedP625 = simplifyClaim(Q2112.claims.P625[0], { keepAll: true }) - simplifiedP214.value.should.be.a.String() - simplifiedP214.id.should.be.a.String() - simplifiedP214.type.should.be.a.String() - simplifiedP214.rank.should.be.a.String() - simplifiedP214.snaktype.should.be.a.String() - simplifiedP214.qualifiers.should.be.an.Object() - simplifiedP214.references.should.be.an.Array() - simplifiedP214.references[0].snaks.P813[0].value.should.deepEqual({ + should(simplifiedP214.value).be.a.String() + should(simplifiedP214.id).be.a.String() + should(simplifiedP214.type).be.a.String() + should(simplifiedP214.rank).be.a.String() + should(simplifiedP214.snaktype).be.a.String() + should(simplifiedP214.qualifiers).be.an.Object() + should(simplifiedP214.references).be.an.Array() + should(simplifiedP214.references[0].snaks.P813[0].value).deepEqual({ time: '2015-08-02T00:00:00.000Z', timezone: 0, before: 0, @@ -561,8 +557,8 @@ describe('simplifyClaim', () => { precision: 11, calendarmodel: 'http://www.wikidata.org/entity/Q1985727', }) - simplifiedP214.references[0].hash.should.be.a.String() - simplifiedP625.value.should.deepEqual({ + should(simplifiedP214.references[0].hash).be.a.String() + should(simplifiedP625.value).deepEqual({ latitude: 52.016666666667, longitude: 8.5166666666667, altitude: null, @@ -573,21 +569,21 @@ describe('simplifyClaim', () => { it('should be overriden by other flags', () => { const simplified = simplifyClaim(Q2112.claims.P214[0], { keepAll: true, keepTypes: false }) - simplified.value.should.be.a.String() - simplified.id.should.be.a.String() + should(simplified.value).be.a.String() + should(simplified.id).be.a.String() should(simplified.type).not.be.ok() - simplified.rank.should.be.a.String() - simplified.snaktype.should.be.a.String() - simplified.qualifiers.should.be.an.Object() - simplified.references.should.be.an.Array() - simplified.references[0].should.be.an.Object() - simplified.references[0].hash.should.be.a.String() + should(simplified.rank).be.a.String() + should(simplified.snaktype).be.a.String() + should(simplified.qualifiers).be.an.Object() + should(simplified.references).be.an.Array() + should(simplified.references[0]).be.an.Object() + should(simplified.references[0].hash).be.a.String() }) }) describe('lexemes', () => { it('should parse lexem claims', () => { - simplifyClaims(L525.claims).should.deepEqual({ + should(simplifyClaims(L525.claims)).deepEqual({ P5185: [ 'Q1775415' ], P5972: [ 'L512-S1' ], }) diff --git a/tests/simplify_entity.ts b/tests/simplify_entity.ts index 92a92fd5..d88ff46c 100644 --- a/tests/simplify_entity.ts +++ b/tests/simplify_entity.ts @@ -1,88 +1,94 @@ -// @ts-nocheck -import 'should' import { cloneDeep, pick } from 'lodash-es' +import should from 'should' import { simplifyEntity, simplifyEntities } from '../src/helpers/simplify_entity.js' import { readJsonFile } from './lib/utils.js' +import type { Item, Lexeme, Property } from '../src/types/entity.js' -const L525 = readJsonFile('./tests/data/L525.json') -const P8098 = readJsonFile('./tests/data/P8098.json') -const Q571 = readJsonFile('./tests/data/Q571.json') +const L525 = readJsonFile('./tests/data/L525.json') as Lexeme +const P8098 = readJsonFile('./tests/data/P8098.json') as Property +const Q571 = readJsonFile('./tests/data/Q571.json') as Item describe('simplify.entity', () => { it('should be a function', () => { - simplifyEntity.should.be.a.Function() + should(simplifyEntity).be.a.Function() }) it('should support items', () => { const Q571Clone = cloneDeep(Q571) const simplifiedEntity = simplifyEntity(Q571Clone) if (simplifiedEntity.type !== 'item') throw new TypeError() - simplifiedEntity.type.should.equal('item') - simplifiedEntity.labels.fr.should.equal('livre') - simplifiedEntity.descriptions.fr.should.equal('document écrit formé de pages reliées entre elles') - simplifiedEntity.aliases.pl.should.be.an.Array() - simplifiedEntity.aliases.pl[0].should.equal('Tom') - simplifiedEntity.claims.P279.should.be.an.Array() - simplifiedEntity.claims.P279[0].should.equal('Q2342494') - simplifiedEntity.sitelinks.afwiki.should.equal('Boek') + should(simplifiedEntity.type).equal('item') + should(simplifiedEntity.labels.fr).equal('livre') + should(simplifiedEntity.descriptions.fr).equal('document écrit formé de pages reliées entre elles') + should(simplifiedEntity.aliases.pl).be.an.Array() + should(simplifiedEntity.aliases.pl[0]).equal('Tom') + should(simplifiedEntity.claims.P279).be.an.Array() + should(simplifiedEntity.claims.P279[0]).equal('Q2342494') + should(simplifiedEntity.sitelinks.afwiki).equal('Boek') }) it('should support properties', () => { const P8098Clone = cloneDeep(P8098) const simplifiedEntity = simplifyEntity(P8098Clone) if (simplifiedEntity.type !== 'property') throw new TypeError() - simplifiedEntity.type.should.equal('property') - simplifiedEntity.datatype.should.equal('external-id') - simplifiedEntity.labels.fr.should.equal('identifiant Biographical Dictionary of Architects in Canada') - simplifiedEntity.descriptions.fr.should.equal("identifiant d'un architecte dans le Biographical Dictionary of Architects in Canada") - simplifiedEntity.aliases.fr.should.be.an.Array() - simplifiedEntity.aliases.fr[0].should.equal('identifiant BDAC') - simplifiedEntity.claims.should.be.an.Object() - simplifiedEntity.claims.P1630.should.be.an.Array() - simplifiedEntity.claims.P1630[0].should.equal('http://dictionaryofarchitectsincanada.org/node/$1') + should(simplifiedEntity.type).equal('property') + should(simplifiedEntity.datatype).equal('external-id') + should(simplifiedEntity.labels.fr).equal('identifiant Biographical Dictionary of Architects in Canada') + should(simplifiedEntity.descriptions.fr).equal("identifiant d'un architecte dans le Biographical Dictionary of Architects in Canada") + should(simplifiedEntity.aliases.fr).be.an.Array() + should(simplifiedEntity.aliases.fr[0]).equal('identifiant BDAC') + should(simplifiedEntity.claims).be.an.Object() + should(simplifiedEntity.claims.P1630).be.an.Array() + should(simplifiedEntity.claims.P1630[0]).equal('http://dictionaryofarchitectsincanada.org/node/$1') }) it('should support lexemes', () => { const L525Clone = cloneDeep(L525) const simplifiedEntity = simplifyEntity(L525Clone) if (simplifiedEntity.type !== 'lexeme') throw new TypeError() - simplifiedEntity.type.should.equal('lexeme') - simplifiedEntity.lemmas.should.be.an.Object() - simplifiedEntity.lemmas.fr.should.equal('maison') - simplifiedEntity.lexicalCategory.should.equal('Q1084') - simplifiedEntity.language.should.equal('Q150') - simplifiedEntity.claims.should.be.an.Object() - simplifiedEntity.claims.P5185[0].should.equal('Q1775415') - simplifiedEntity.forms.should.be.an.Object() - simplifiedEntity.forms[0].claims.P443[0].should.equal('LL-Q150 (fra)-0x010C-maisons.wav') - simplifiedEntity.senses.should.be.an.Object() - simplifiedEntity.senses[0].glosses.fr.should.equal("édifice destiné à l'habitation") - simplifiedEntity.senses[0].claims.P5137[0].should.equal('Q3947') + should(simplifiedEntity.type).equal('lexeme') + should(simplifiedEntity.lemmas).be.an.Object() + should(simplifiedEntity.lemmas.fr).equal('maison') + should(simplifiedEntity.lexicalCategory).equal('Q1084') + should(simplifiedEntity.language).equal('Q150') + should(simplifiedEntity.claims).be.an.Object() + should(simplifiedEntity.claims.P5185[0]).equal('Q1775415') + should(simplifiedEntity.forms).be.an.Object() + should(simplifiedEntity.forms[0].claims.P443[0]).equal('LL-Q150 (fra)-0x010C-maisons.wav') + should(simplifiedEntity.senses).be.an.Object() + should(simplifiedEntity.senses[0].glosses.fr).equal("édifice destiné à l'habitation") + should(simplifiedEntity.senses[0].claims.P5137[0]).equal('Q3947') }) it('should pass options down to subfunctions', () => { const Q571Clone = cloneDeep(Q571) const simplifiedEntity = simplifyEntity(Q571Clone, { keepQualifiers: true, keepIds: true, addUrl: true }) - simplifiedEntity.labels.fr.should.equal('livre') - simplifiedEntity.descriptions.fr.should.equal('document écrit formé de pages reliées entre elles') - simplifiedEntity.aliases.pl.should.be.an.Array() - simplifiedEntity.aliases.pl[0].should.equal('Tom') - simplifiedEntity.claims.P279.should.be.an.Array() - simplifiedEntity.claims.P279[0].should.be.an.Object() - simplifiedEntity.claims.P279[0].value.should.equal('Q2342494') - simplifiedEntity.sitelinks.afwiki.should.be.an.Object() - simplifiedEntity.sitelinks.afwiki.title.should.equal('Boek') - simplifiedEntity.sitelinks.afwiki.url.should.equal('https://af.wikipedia.org/wiki/Boek') + if (simplifiedEntity.type !== 'item') throw new TypeError('should be item') + should(simplifiedEntity.labels.fr).equal('livre') + should(simplifiedEntity.descriptions.fr).equal('document écrit formé de pages reliées entre elles') + should(simplifiedEntity.aliases.pl).be.an.Array() + should(simplifiedEntity.aliases.pl[0]).equal('Tom') + should(simplifiedEntity.claims.P279).be.an.Array() + should(simplifiedEntity.claims.P279[0]).be.an.Object() + // @ts-expect-error + should(simplifiedEntity.claims.P279[0].value).equal('Q2342494') + should(simplifiedEntity.sitelinks.afwiki).be.an.Object() + // @ts-expect-error + should(simplifiedEntity.sitelinks.afwiki.title).equal('Boek') + // @ts-expect-error + should(simplifiedEntity.sitelinks.afwiki.url).equal('https://af.wikipedia.org/wiki/Boek') }) it('should accept partial entities', () => { const Q571Clone = cloneDeep(Q571) + // @ts-expect-error very very partial entity const emptyEntity = simplifyEntity({}) - Object.keys(emptyEntity).length.should.equal(3) + should(Object.keys(emptyEntity).length).equal(3) const partialEntity = simplifyEntity(pick(Q571Clone, 'id', 'type', 'labels')) - Object.keys(partialEntity).length.should.equal(4) - partialEntity.labels.should.be.an.Object() - partialEntity.labels.fr.should.equal('livre') + if (partialEntity.type !== 'item') throw new TypeError('should be item') + should(Object.keys(partialEntity).length).equal(4) + should(partialEntity.labels).be.an.Object() + should(partialEntity.labels.fr).equal('livre') }) }) @@ -91,12 +97,13 @@ describe('simplify.entities', () => { const Q571Clone = cloneDeep(Q571) const entities = { Q571: Q571Clone } const simplifiedEntities = simplifyEntities(entities) - simplifiedEntities.Q571.labels.fr.should.equal('livre') - simplifiedEntities.Q571.descriptions.fr.should.equal('document écrit formé de pages reliées entre elles') - simplifiedEntities.Q571.aliases.pl.should.be.an.Array() - simplifiedEntities.Q571.aliases.pl[0].should.equal('Tom') - simplifiedEntities.Q571.claims.P279.should.be.an.Array() - simplifiedEntities.Q571.claims.P279[0].should.equal('Q2342494') - simplifiedEntities.Q571.sitelinks.afwiki.should.equal('Boek') + if (simplifiedEntities.Q571.type !== 'item') throw new TypeError('should be item') + should(simplifiedEntities.Q571.labels.fr).equal('livre') + should(simplifiedEntities.Q571.descriptions.fr).equal('document écrit formé de pages reliées entre elles') + should(simplifiedEntities.Q571.aliases.pl).be.an.Array() + should(simplifiedEntities.Q571.aliases.pl[0]).equal('Tom') + should(simplifiedEntities.Q571.claims.P279).be.an.Array() + should(simplifiedEntities.Q571.claims.P279[0]).equal('Q2342494') + should(simplifiedEntities.Q571.sitelinks.afwiki).equal('Boek') }) }) diff --git a/tests/simplify_forms.ts b/tests/simplify_forms.ts index 42d46ce8..e1ed741e 100644 --- a/tests/simplify_forms.ts +++ b/tests/simplify_forms.ts @@ -1,27 +1,28 @@ -// @ts-nocheck -import 'should' +import should from 'should' import { simplifyForms, simplifyForm } from '../src/helpers/simplify_forms.js' import { readJsonFile } from './lib/utils.js' +import type { Lexeme } from '../src/types/entity.js' -const L525 = readJsonFile('./tests/data/L525.json') +const L525 = readJsonFile('./tests/data/L525.json') as Lexeme describe('simplify.form', () => { it('should reject an object that isnt a form', () => { - simplifyForm.bind(null, {}).should.throw('invalid form object') + // @ts-expect-error not a form + should(() => simplifyForm({})).throw('invalid form object') }) it('should simplify a form', () => { const simplifiedForm = simplifyForm(L525.forms[0]) - simplifiedForm.representations.fr.should.equal('maisons') - simplifiedForm.grammaticalFeatures[0].should.equal('Q146786') - simplifiedForm.claims.should.deepEqual({ P443: [ 'LL-Q150 (fra)-0x010C-maisons.wav' ] }) + should(simplifiedForm.representations.fr).equal('maisons') + should(simplifiedForm.grammaticalFeatures[0]).equal('Q146786') + should(simplifiedForm.claims).deepEqual({ P443: [ 'LL-Q150 (fra)-0x010C-maisons.wav' ] }) }) it('should pass down options', () => { const simplifiedForm = simplifyForm(L525.forms[0], { keepIds: true }) - simplifiedForm.representations.fr.should.equal('maisons') - simplifiedForm.grammaticalFeatures[0].should.equal('Q146786') - simplifiedForm.claims.should.deepEqual({ + should(simplifiedForm.representations.fr).equal('maisons') + should(simplifiedForm.grammaticalFeatures[0]).equal('Q146786') + should(simplifiedForm.claims).deepEqual({ P443: [ { id: 'L525-F1$079bdca7-5130-4f9f-bac9-e8d032c38263', @@ -35,13 +36,14 @@ describe('simplify.form', () => { describe('simplify.forms', () => { it('should simplify forms', () => { const simplifiedForms = simplifyForms(L525.forms) - simplifiedForms.should.be.an.Array() - simplifiedForms.should.deepEqual(L525.forms.map(simplifyForm)) + should(simplifiedForms).be.an.Array() + should(simplifiedForms).deepEqual(L525.forms.map(form => simplifyForm(form))) }) it('should pass down options', () => { const simplifiedForms = simplifyForms(L525.forms, { keepIds: true }) - simplifiedForms.should.be.an.Array() - simplifiedForms[0].claims.P443[0].id.should.equal('L525-F1$079bdca7-5130-4f9f-bac9-e8d032c38263') + should(simplifiedForms).be.an.Array() + // @ts-expect-error simplified claims can be a lot of things + should(simplifiedForms[0].claims.P443[0].id).equal('L525-F1$079bdca7-5130-4f9f-bac9-e8d032c38263') }) }) diff --git a/tests/simplify_qualifiers.ts b/tests/simplify_qualifiers.ts index b47e21d6..7cdca6b5 100644 --- a/tests/simplify_qualifiers.ts +++ b/tests/simplify_qualifiers.ts @@ -1,31 +1,33 @@ import should from 'should' import { simplifyQualifier, simplifyPropertyQualifiers, simplifyQualifiers } from '../src/helpers/simplify_claims.js' import { readJsonFile } from './lib/utils.js' +import type { TimeConverter, TimeConverterFn } from '../src/helpers/wikibase_time.js' +import type { Item } from '../src/types/entity.js' -const Q19180293 = readJsonFile('./tests/data/Q19180293.json') -const Q2112 = readJsonFile('./tests/data/Q2112.json') -const Q571 = readJsonFile('./tests/data/Q571.json') +const Q19180293 = readJsonFile('./tests/data/Q19180293.json') as Item +const Q2112 = readJsonFile('./tests/data/Q2112.json') as Item +const Q571 = readJsonFile('./tests/data/Q571.json') as Item describe('simplifyQualifier', () => { it('should simplify a qualifier', () => { const qualifier = Q2112.claims.P190[1].qualifiers.P580[0] const simplified = simplifyQualifier(qualifier) - simplified.should.equal('1953-01-01T00:00:00.000Z') + should(simplified).equal('1953-01-01T00:00:00.000Z') }) describe('empty values', () => { it('should return the desired novalueValue', () => { const noValueQualifier = Q19180293.claims.P1433[0].qualifiers.P1100[0] should(simplifyQualifier(noValueQualifier)).not.be.ok() - simplifyQualifier(noValueQualifier, { novalueValue: '-' }).should.equal('-') - simplifyQualifier(noValueQualifier, { novalueValue: '' }).should.equal('') + should(simplifyQualifier(noValueQualifier, { novalueValue: '-' })).equal('-') + should(simplifyQualifier(noValueQualifier, { novalueValue: '' })).equal('') }) it('should return the desired somevalueValue', () => { const someValueQualifier = Q19180293.claims.P1433[0].qualifiers.P156[0] should(simplifyQualifier(someValueQualifier)).not.be.ok() - simplifyQualifier(someValueQualifier, { somevalueValue: '?' }).should.equal('?') - simplifyQualifier(someValueQualifier, { somevalueValue: '' }).should.equal('') + should(simplifyQualifier(someValueQualifier, { somevalueValue: '?' })).equal('?') + should(simplifyQualifier(someValueQualifier, { somevalueValue: '' })).equal('') }) it('should accept null as a possible value', () => { @@ -37,13 +39,18 @@ describe('simplifyQualifier', () => { describe('time', () => { it('should respect timeConverter for qualifiers claims', () => { const qualifier = Q571.claims.P1709[0].qualifiers.P813[0] - const timeClaim = timeConverter => simplifyQualifier(qualifier, { timeConverter }) - timeClaim('iso').should.equal('2015-06-11T00:00:00.000Z') - timeClaim('epoch').should.equal(1433980800000) - timeClaim('simple-day').should.equal('2015-06-11') - timeClaim('none').should.equal('+2015-06-11T00:00:00Z') - const timeConverterFn = ({ time, precision }) => `foo/${time}/${precision}/bar` - timeClaim(timeConverterFn).should.equal('foo/+2015-06-11T00:00:00Z/11/bar') + const timeClaim = (timeConverter: TimeConverter | TimeConverterFn) => simplifyQualifier(qualifier, { timeConverter }) + should(timeClaim('iso')).equal('2015-06-11T00:00:00.000Z') + should(timeClaim('epoch')).equal(1433980800000) + should(timeClaim('simple-day')).equal('2015-06-11') + should(timeClaim('none')).equal('+2015-06-11T00:00:00Z') + should(timeClaim( + timeObj => { + if (typeof timeObj !== 'object') throw new Error('expect WikibaseTimeObject') + const { time, precision } = timeObj + return `foo/${time}/${precision}/bar` + }, + )).equal('foo/+2015-06-11T00:00:00Z/11/bar') }) }) }) @@ -52,25 +59,25 @@ describe('simplifyPropertyQualifiers', () => { it('should simplify propertyQualifiers', () => { const propertyQualifiers = Q2112.claims.P190[1].qualifiers.P580 const simplified = simplifyPropertyQualifiers(propertyQualifiers) - simplified.should.deepEqual([ '1953-01-01T00:00:00.000Z' ]) + should(simplified).deepEqual([ '1953-01-01T00:00:00.000Z' ]) }) describe('empty values', () => { it('should return the desired novalueValue', () => { const propQualifiers = Q19180293.claims.P1433[0].qualifiers.P1100 - simplifyPropertyQualifiers(propQualifiers, { novalueValue: '-' }).should.deepEqual([ '-' ]) - simplifyPropertyQualifiers(propQualifiers, { novalueValue: '' }).should.deepEqual([ '' ]) + should(simplifyPropertyQualifiers(propQualifiers, { novalueValue: '-' })).deepEqual([ '-' ]) + should(simplifyPropertyQualifiers(propQualifiers, { novalueValue: '' })).deepEqual([ '' ]) }) it('should return the desired somevalueValue', () => { const propQualifiers = Q19180293.claims.P1433[0].qualifiers.P156 - simplifyPropertyQualifiers(propQualifiers, { somevalueValue: '?' }).should.deepEqual([ '?' ]) - simplifyPropertyQualifiers(propQualifiers, { somevalueValue: '' }).should.deepEqual([ '' ]) + should(simplifyPropertyQualifiers(propQualifiers, { somevalueValue: '?' })).deepEqual([ '?' ]) + should(simplifyPropertyQualifiers(propQualifiers, { somevalueValue: '' })).deepEqual([ '' ]) }) it('should accept null as a possible value', () => { const propQualifiers = Q19180293.claims.P1433[0].qualifiers.P1100 - simplifyPropertyQualifiers(propQualifiers, { novalueValue: null }).should.deepEqual([ null ]) + should(simplifyPropertyQualifiers(propQualifiers, { novalueValue: null })).deepEqual([ null ]) }) }) }) @@ -79,31 +86,30 @@ describe('simplifyQualifiers', () => { it('should simplify qualifiers', () => { const qualifiers = Q2112.claims.P190[1].qualifiers const simplified = simplifyQualifiers(qualifiers) - simplified.P580.should.deepEqual([ '1953-01-01T00:00:00.000Z' ]) + should(simplified.P580).deepEqual([ '1953-01-01T00:00:00.000Z' ]) }) describe('empty values', () => { it('should return the desired novalueValue', () => { const qualifiers = Q19180293.claims.P1433[0].qualifiers - simplifyQualifiers(qualifiers, { novalueValue: '-' }).P1100.should.deepEqual([ '-' ]) - simplifyQualifiers(qualifiers, { novalueValue: '' }).P1100.should.deepEqual([ '' ]) + should(simplifyQualifiers(qualifiers, { novalueValue: '-' }).P1100).deepEqual([ '-' ]) + should(simplifyQualifiers(qualifiers, { novalueValue: '' }).P1100).deepEqual([ '' ]) }) it('should return the desired somevalueValue', () => { const qualifiers = Q19180293.claims.P1433[0].qualifiers - simplifyQualifiers(qualifiers, { somevalueValue: '?' }).P156.should.deepEqual([ '?' ]) - simplifyQualifiers(qualifiers, { somevalueValue: '' }).P156.should.deepEqual([ '' ]) + should(simplifyQualifiers(qualifiers, { somevalueValue: '?' }).P156).deepEqual([ '?' ]) + should(simplifyQualifiers(qualifiers, { somevalueValue: '' }).P156).deepEqual([ '' ]) }) it('should accept null as a possible value', () => { const qualifiers = Q19180293.claims.P1433[0].qualifiers - simplifyQualifiers(qualifiers, { novalueValue: null }).P1100.should.deepEqual([ null ]) + should(simplifyQualifiers(qualifiers, { novalueValue: null }).P1100).deepEqual([ null ]) }) it('should keep snaktype if requested', () => { const qualifier = Q19180293.claims.P1433[0].qualifiers.P1100[0] - simplifyQualifier(qualifier, { keepSnaktypes: true }) - .should.deepEqual({ value: undefined, snaktype: 'novalue' }) + should(simplifyQualifier(qualifier, { keepSnaktypes: true })).deepEqual({ value: undefined, snaktype: 'novalue' }) }) }) }) diff --git a/tests/simplify_references.ts b/tests/simplify_references.ts index 0f70c185..6fd55361 100644 --- a/tests/simplify_references.ts +++ b/tests/simplify_references.ts @@ -1,13 +1,13 @@ -import 'should' +import should from 'should' import { simplifyReferences } from '../src/helpers/simplify_claims.js' import { readJsonFile } from './lib/utils.js' +import type { Item } from '../src/types/entity.js' -const Q217447 = readJsonFile('./tests/data/Q217447.json') +const Q217447 = readJsonFile('./tests/data/Q217447.json') as Item describe('simplifyReferences', () => { it('should simplify references', () => { // @ts-expect-error - simplifyReferences(Q217447.claims.P131[0].references) - .should.deepEqual([ { P248: [ 'Q3485482' ] } ]) + should(simplifyReferences(Q217447.claims.P131[0].references)).deepEqual([ { P248: [ 'Q3485482' ] } ]) }) }) diff --git a/tests/simplify_senses.ts b/tests/simplify_senses.ts index af673420..37045642 100644 --- a/tests/simplify_senses.ts +++ b/tests/simplify_senses.ts @@ -1,25 +1,26 @@ -// @ts-nocheck -import 'should' +import should from 'should' import { simplifySenses, simplifySense } from '../src/helpers/simplify_senses.js' import { readJsonFile } from './lib/utils.js' +import type { Lexeme } from '../src/types/entity.js' -const L525 = readJsonFile('./tests/data/L525.json') +const L525 = readJsonFile('./tests/data/L525.json') as Lexeme describe('simplify.sense', () => { it('should reject an object that isnt a sense', () => { - simplifySense.bind(null, {}).should.throw('invalid sense object') + // @ts-expect-error invalid sense + should(() => simplifySense({})).throw('invalid sense object') }) it('should simplify a sense', () => { const simplifiedSense = simplifySense(L525.senses[0]) - simplifiedSense.glosses.fr.should.equal("édifice destiné à l'habitation") - simplifiedSense.claims.should.deepEqual({ P5137: [ 'Q3947' ] }) + should(simplifiedSense.glosses.fr).equal("édifice destiné à l'habitation") + should(simplifiedSense.claims).deepEqual({ P5137: [ 'Q3947' ] }) }) it('should pass down options', () => { const simplifiedSense = simplifySense(L525.senses[0], { keepIds: true }) - simplifiedSense.glosses.fr.should.equal("édifice destiné à l'habitation") - simplifiedSense.claims.should.deepEqual({ + should(simplifiedSense.glosses.fr).equal("édifice destiné à l'habitation") + should(simplifiedSense.claims).deepEqual({ P5137: [ { id: 'L525-S1$66D20252-8CEC-4DB1-8B00-D713CFF42E48', @@ -33,13 +34,14 @@ describe('simplify.sense', () => { describe('simplify.senses', () => { it('should simplify senses', () => { const simplifiedSenses = simplifySenses(L525.senses) - simplifiedSenses.should.be.an.Array() - simplifiedSenses.should.deepEqual(L525.senses.map(simplifySense)) + should(simplifiedSenses).be.an.Array() + should(simplifiedSenses).deepEqual(L525.senses.map(sense => simplifySense(sense))) }) it('should pass down options', () => { const simplifiedSenses = simplifySenses(L525.senses, { keepIds: true }) - simplifiedSenses.should.be.an.Array() - simplifiedSenses[0].claims.P5137[0].id.should.equal('L525-S1$66D20252-8CEC-4DB1-8B00-D713CFF42E48') + should(simplifiedSenses).be.an.Array() + // @ts-expect-error simplified claims can be a lot of things + should(simplifiedSenses[0].claims.P5137[0].id).equal('L525-S1$66D20252-8CEC-4DB1-8B00-D713CFF42E48') }) }) diff --git a/tests/simplify_sitelinks.ts b/tests/simplify_sitelinks.ts index 5f769961..9cd0bf84 100644 --- a/tests/simplify_sitelinks.ts +++ b/tests/simplify_sitelinks.ts @@ -1,32 +1,36 @@ -// @ts-nocheck import should from 'should' import { simplifySitelinks } from '../src/helpers/simplify_sitelinks.js' import { readJsonFile, objLenght } from './lib/utils.js' +import type { Item } from '../src/types/entity.js' -const Q571 = readJsonFile('./tests/data/Q571.json') +const Q571 = readJsonFile('./tests/data/Q571.json') as Item describe('simplify.sitelinks', () => { it('should simplify sitelinks', () => { const simplifiedSitelinks = simplifySitelinks(Q571.sitelinks) - simplifiedSitelinks.enwiki.should.equal('Book') - simplifiedSitelinks.frwiki.should.equal('Livre (document)') - objLenght(simplifiedSitelinks).should.equal(objLenght(Q571.sitelinks)) + should(simplifiedSitelinks.enwiki).equal('Book') + should(simplifiedSitelinks.frwiki).equal('Livre (document)') + should(objLenght(simplifiedSitelinks)).equal(objLenght(Q571.sitelinks)) }) it('should preserve badges if requested with keepBadges=true', () => { const simplifiedSitelinks = simplifySitelinks(Q571.sitelinks, { keepBadges: true }) - simplifiedSitelinks.enwiki.title.should.equal('Book') - simplifiedSitelinks.enwiki.badges.should.deepEqual([]) - simplifiedSitelinks.lawiki.title.should.equal('Liber') - simplifiedSitelinks.lawiki.badges.should.deepEqual([ 'Q17437796' ]) + if (typeof simplifiedSitelinks.enwiki != 'object') throw new Error('has to be an object') + should(simplifiedSitelinks.enwiki.title).equal('Book') + should(simplifiedSitelinks.enwiki.badges).deepEqual([]) + if (typeof simplifiedSitelinks.lawiki != 'object') throw new Error('has to be an object') + should(simplifiedSitelinks.lawiki.title).equal('Liber') + should(simplifiedSitelinks.lawiki.badges).deepEqual([ 'Q17437796' ]) }) it('should preserve badges if requested with keepAll=true', () => { const simplifiedSitelinks = simplifySitelinks(Q571.sitelinks, { keepBadges: true }) - simplifiedSitelinks.enwiki.title.should.equal('Book') - simplifiedSitelinks.enwiki.badges.should.deepEqual([]) - simplifiedSitelinks.lawiki.title.should.equal('Liber') - simplifiedSitelinks.lawiki.badges.should.deepEqual([ 'Q17437796' ]) + if (typeof simplifiedSitelinks.enwiki != 'object') throw new Error('has to be an object') + should(simplifiedSitelinks.enwiki.title).equal('Book') + should(simplifiedSitelinks.enwiki.badges).deepEqual([]) + if (typeof simplifiedSitelinks.lawiki != 'object') throw new Error('has to be an object') + should(simplifiedSitelinks.lawiki.title).equal('Liber') + should(simplifiedSitelinks.lawiki.badges).deepEqual([ 'Q17437796' ]) }) it('should create a different object', () => { @@ -34,12 +38,13 @@ describe('simplify.sitelinks', () => { }) it('should return an object with a URL if requested ', () => { - simplifySitelinks(Q571.sitelinks, { addUrl: true }).enwiki.url.should.equal('https://en.wikipedia.org/wiki/Book') + const result = simplifySitelinks(Q571.sitelinks, { addUrl: true }).enwiki + if (typeof result != 'object') throw new Error('has to be an object') + should(result.url).equal('https://en.wikipedia.org/wiki/Book') }) it('should not throw when a sitelink is null ', () => { const sitelinks = { frwiki: null } - // @ts-expect-error - simplifySitelinks(sitelinks).should.deepEqual(sitelinks) + should(simplifySitelinks(sitelinks)).deepEqual(sitelinks) }) }) diff --git a/tests/simplify_sparql_results.ts b/tests/simplify_sparql_results.ts index 34e8ab0e..fafc44e3 100644 --- a/tests/simplify_sparql_results.ts +++ b/tests/simplify_sparql_results.ts @@ -1,66 +1,69 @@ -// @ts-nocheck import { cloneDeep } from 'lodash-es' import should from 'should' import { isEntityId, isGuid } from '../src/helpers/helpers.js' import { simplifySparqlResults } from '../src/helpers/simplify_sparql_results.js' import { readJsonFile } from './lib/utils.js' +import type { SparqlResults } from '../src/types/sparql.js' -const multiVarsData = readJsonFile('./tests/data/multi_vars_sparql_results.json') -const noDatatypeData = readJsonFile('./tests/data/no_datatype_sparql_results.json') -const propertiesList = readJsonFile('./tests/data/properties_list.json') -const resultsWithLabelsDescriptionsAndAliases = readJsonFile('./tests/data/results_with_labels_descriptions_and_aliases.json') -const singleVarData = readJsonFile('./tests/data/single_var_sparql_results.json') -const sparqlResultsWithNestedAssociatedVariables = readJsonFile('./tests/data/sparql_results_with_nested_associated_variables.json') -const sparqlResultsWithOptionalValues = readJsonFile('./tests/data/sparql_results_with_optional_values.json') -const sparqlResultsWithStatements = readJsonFile('./tests/data/sparql_results_with_statements.json') +const multiVarsData = readJsonFile('./tests/data/multi_vars_sparql_results.json') as SparqlResults +const noDatatypeData = readJsonFile('./tests/data/no_datatype_sparql_results.json') as SparqlResults +const propertiesList = readJsonFile('./tests/data/properties_list.json') as SparqlResults +const resultsWithLabelsDescriptionsAndAliases = readJsonFile('./tests/data/results_with_labels_descriptions_and_aliases.json') as SparqlResults +const singleVarData = readJsonFile('./tests/data/single_var_sparql_results.json') as SparqlResults +const sparqlResultsWithNestedAssociatedVariables = readJsonFile('./tests/data/sparql_results_with_nested_associated_variables.json') as SparqlResults +const sparqlResultsWithOptionalValues = readJsonFile('./tests/data/sparql_results_with_optional_values.json') as SparqlResults +const sparqlResultsWithStatements = readJsonFile('./tests/data/sparql_results_with_statements.json') as SparqlResults describe('wikidata simplify SPARQL results', () => { describe('common', () => { it('should return a plain object', () => { - simplifySparqlResults(singleVarData).should.be.an.Array() - simplifySparqlResults(multiVarsData).should.be.an.Array() + should(simplifySparqlResults(singleVarData)).be.an.Array() + should(simplifySparqlResults(multiVarsData)).be.an.Array() }) it('should parse the input if passed a JSON string', () => { const json = JSON.stringify(singleVarData) - simplifySparqlResults(json).should.be.an.Array() + // @ts-expect-error json is a string and not in the object form + should(simplifySparqlResults(json)).be.an.Array() const json2 = JSON.stringify(multiVarsData) - simplifySparqlResults(json2).should.be.an.Array() + // @ts-expect-error json is a string and not in the object form + should(simplifySparqlResults(json2)).be.an.Array() }) }) it('should return an array of results objects', () => { const output = simplifySparqlResults(multiVarsData) - output[0].should.be.an.Object() - output[0].entity.value.should.equal('Q3731207') - output[0].entity.label.should.equal('Ercole Patti') - output[0].year.should.equal(1903) + should(output[0]).be.an.Object() + if (typeof output[0].entity !== 'object') throw new Error('has to be an object') + should(output[0].entity.value).equal('Q3731207') + should(output[0].entity.label).equal('Ercole Patti') + should(output[0].year).equal(1903) }) it('should not throw when the datatype is missing', () => { const output = simplifySparqlResults(noDatatypeData) - output[0].year.should.equal('1937') + should(output[0].year).equal('1937') }) it('should not throw when an optional variable has no result', () => { const result = simplifySparqlResults(sparqlResultsWithOptionalValues)[0] - result.composer.should.be.an.Object() + should(result.composer).be.an.Object() should(result.genre).not.be.ok() }) describe('minimize', () => { it('should return an array of results values, filtering out blank nodes', () => { const output = simplifySparqlResults(singleVarData, { minimize: true }) - output[0].should.equal('Q112983') - output.forEach(result => isEntityId(result).should.be.true()) + should(output[0]).equal('Q112983') + output.forEach(result => should(typeof result === 'string' && isEntityId(result)).be.true()) }) it('should return an array of results value object', () => { - const output = simplifySparqlResults(singleVarData, { minimize: false }) - output[0].should.deepEqual({ genre: 'Q112983' }) + const output = simplifySparqlResults(singleVarData) + should(output[0]).deepEqual({ genre: 'Q112983' }) output.forEach(result => { - result.should.be.an.Object() - if (result.genre) isEntityId(result.genre).should.be.true() + should(result).be.an.Object() + if (result.genre) should(typeof result.genre === 'string' && isEntityId(result.genre)).be.true() }) }) }) @@ -70,15 +73,16 @@ describe('wikidata simplify SPARQL results', () => { const results = simplifySparqlResults(resultsWithLabelsDescriptionsAndAliases) resultsWithLabelsDescriptionsAndAliases.results.bindings.forEach((rawResult, i) => { const simplified = results[i] - simplified.item.should.be.an.Object() - simplified.item.value.should.be.a.String() - if (rawResult.itemLabel) simplified.item.label.should.be.a.String() - if (rawResult.itemDescription) simplified.item.description.should.be.a.String() - if (rawResult.itemAltLabel) simplified.item.aliases.should.be.a.String() + should(simplified.item).be.an.Object() + if (typeof simplified.item != 'object') throw new Error('has to be an object') + should(simplified.item.value).be.a.String() + if (rawResult.itemLabel) should(simplified.item.label).be.a.String() + if (rawResult.itemDescription) should(simplified.item.description).be.a.String() + if (rawResult.itemAltLabel) should(simplified.item.aliases).be.a.String() should(simplified.itemLabel).not.be.ok() should(simplified.itemDescription).not.be.ok() should(simplified.itemAltLabel).not.be.ok() - simplified.pseudonyme.should.a.String() + should(simplified.pseudonyme).a.String() }) }) @@ -86,9 +90,10 @@ describe('wikidata simplify SPARQL results', () => { const results = simplifySparqlResults(propertiesList) propertiesList.results.bindings.forEach((rawResult, i) => { const simplified = results[i] - simplified.property.should.be.an.Object() - simplified.property.value.should.be.a.String() - if (rawResult.propertyType) simplified.property.type.should.be.a.String() + should(simplified.property).be.an.Object() + if (typeof simplified.property != 'object') throw new Error('has to be an object') + should(simplified.property.value).be.a.String() + if (rawResult.propertyType) should(simplified.property.type).be.a.String() should(simplified.propertyType).not.be.ok() }) }) @@ -100,11 +105,12 @@ describe('wikidata simplify SPARQL results', () => { const results = simplifySparqlResults(rawResults) rawResults.results.bindings.forEach((rawResult, i) => { const simplified = results[i] - simplified.item.should.be.an.Object() - simplified.item.value.should.be.a.String() - if (rawResult.itemDescription) simplified.item.description.should.be.a.String() - if (rawResult.itemAltLabel) simplified.item.aliases.should.be.a.String() - simplified.pseudonyme.should.a.String() + should(simplified.item).be.an.Object() + if (typeof simplified.item != 'object') throw new Error('has to be an object') + should(simplified.item.value).be.a.String() + if (rawResult.itemDescription) should(simplified.item.description).be.a.String() + if (rawResult.itemAltLabel) should(simplified.item.aliases).be.a.String() + should(simplified.pseudonyme).a.String() }) }) @@ -115,19 +121,19 @@ describe('wikidata simplify SPARQL results', () => { const results = simplifySparqlResults(rawResults) rawResults.results.bindings.forEach((rawResult, i) => { const simplified = results[i] - simplified.pseudonyme.should.be.a.String() + should(simplified.pseudonyme).be.a.String() should(simplified.item).not.be.ok() - if (rawResult.itemLabel) simplified.itemLabel.should.be.a.String() - if (rawResult.itemDescription) simplified.itemDescription.should.be.a.String() - if (rawResult.itemAltLabel) simplified.itemAltLabel.should.be.a.String() - simplified.pseudonyme.should.a.String() + if (rawResult.itemLabel) should(simplified.itemLabel).be.a.String() + if (rawResult.itemDescription) should(simplified.itemDescription).be.a.String() + if (rawResult.itemAltLabel) should(simplified.itemAltLabel).be.a.String() + should(simplified.pseudonyme).a.String() }) }) it('should ignore nested associated variables', () => { const rawResults = cloneDeep(sparqlResultsWithNestedAssociatedVariables) const results = simplifySparqlResults(rawResults, { minimize: true }) - results.length.should.equal(2) + should(results.length).equal(2) }) }) @@ -135,7 +141,7 @@ describe('wikidata simplify SPARQL results', () => { it('should convert statement URIs into claims GUIDs', () => { const rawResults = cloneDeep(sparqlResultsWithStatements) const results = simplifySparqlResults(rawResults, { minimize: true }) - results.forEach(result => isGuid(result).should.be.true()) + results.forEach(result => should(typeof result === 'string' && isGuid(result)).be.true()) }) }) }) diff --git a/tests/simplify_text_attributes.ts b/tests/simplify_text_attributes.ts index 2d6b5d30..a3a44354 100644 --- a/tests/simplify_text_attributes.ts +++ b/tests/simplify_text_attributes.ts @@ -1,18 +1,20 @@ import should from 'should' import { simplifyAliases, simplifyDescriptions, simplifyLabels } from '../src/helpers/simplify_text_attributes.js' import { readJsonFile, objLenght } from './lib/utils.js' +import type { Item } from '../src/types/entity.js' -const Q571 = readJsonFile('./tests/data/Q571.json') +const Q571 = readJsonFile('./tests/data/Q571.json') as Item describe('simplifyLabels', () => { it('should simplify labels', () => { const simplifiedLabels = simplifyLabels(Q571.labels) - simplifiedLabels.en.should.equal('book') - simplifiedLabels.fr.should.equal('livre') - objLenght(simplifiedLabels).should.equal(objLenght(Q571.labels)) + should(simplifiedLabels.en).equal('book') + should(simplifiedLabels.fr).equal('livre') + should(objLenght(simplifiedLabels)).equal(objLenght(Q571.labels)) }) it('should create a different object', () => { + // @ts-expect-error types are also different should(simplifyLabels(Q571.labels) === Q571.labels).be.false() }) @@ -21,19 +23,20 @@ describe('simplifyLabels', () => { // and set it to null in absence of value // Known case in wikibase-cli: wd data --props labels.en --simplify const entityWithNullEnLabel = { labels: { en: null } } - simplifyLabels(entityWithNullEnLabel.labels).should.deepEqual({ en: null }) + should(simplifyLabels(entityWithNullEnLabel.labels)).deepEqual({ en: null }) }) }) describe('simplifyDescriptions', () => { it('should simplify descriptions', () => { const simplifiedDescriptions = simplifyDescriptions(Q571.descriptions) - simplifiedDescriptions.en.slice(0, 23).should.equal('medium for a collection') - simplifiedDescriptions.fr.slice(0, 14).should.equal('document écrit') - objLenght(simplifiedDescriptions).should.equal(objLenght(Q571.descriptions)) + should(simplifiedDescriptions.en.slice(0, 23)).equal('medium for a collection') + should(simplifiedDescriptions.fr.slice(0, 14)).equal('document écrit') + should(objLenght(simplifiedDescriptions)).equal(objLenght(Q571.descriptions)) }) it('should create a different object', () => { + // @ts-expect-error types are also different should(simplifyLabels(Q571.descriptions) === Q571.descriptions).be.false() }) }) @@ -41,14 +44,15 @@ describe('simplifyDescriptions', () => { describe('simplifyAliases', () => { it('should simplify aliases', () => { const simplifiedAliases = simplifyAliases(Q571.aliases) - objLenght(simplifiedAliases.en).should.equal(objLenght(Q571.aliases.en)) - objLenght(simplifiedAliases.fr).should.equal(objLenght(Q571.aliases.fr)) - simplifiedAliases.en[0].should.equal('books') - simplifiedAliases.fr[0].should.equal('ouvrage') - objLenght(simplifiedAliases).should.equal(objLenght(Q571.aliases)) + should(objLenght(simplifiedAliases.en)).equal(objLenght(Q571.aliases.en)) + should(objLenght(simplifiedAliases.fr)).equal(objLenght(Q571.aliases.fr)) + should(simplifiedAliases.en[0]).equal('books') + should(simplifiedAliases.fr[0]).equal('ouvrage') + should(objLenght(simplifiedAliases)).equal(objLenght(Q571.aliases)) }) it('should create a different object', () => { + // @ts-expect-error types are also different should(simplifyAliases(Q571.aliases) === Q571.aliases).be.false() }) }) diff --git a/tests/sitelinks_helpers.ts b/tests/sitelinks_helpers.ts index 2646cf68..590dd417 100644 --- a/tests/sitelinks_helpers.ts +++ b/tests/sitelinks_helpers.ts @@ -1,182 +1,153 @@ -import 'should' +import should from 'should' import { getSitelinkUrl, getSitelinkData, isSitelinkKey } from '../src/helpers/sitelinks.js' describe('getSitelinkUrl', () => { it('should be a function', () => { - getSitelinkUrl.should.be.a.Function() + should(getSitelinkUrl).be.a.Function() }) it('should return a sitelink URL', () => { - getSitelinkUrl({ site: 'commons', title: 'Lyon' }) - .should.equal('https://commons.wikimedia.org/wiki/Lyon') + should(getSitelinkUrl({ site: 'commons', title: 'Lyon' })).equal('https://commons.wikimedia.org/wiki/Lyon') - getSitelinkUrl({ site: 'frwiki', title: 'Septembre' }) - .should.equal('https://fr.wikipedia.org/wiki/Septembre') + should(getSitelinkUrl({ site: 'frwiki', title: 'Septembre' })).equal('https://fr.wikipedia.org/wiki/Septembre') - getSitelinkUrl({ site: 'hywikisource', title: 'Հեղինակ:Վիկտոր Հյուգո' }) - .should.equal('https://hy.wikisource.org/wiki/%D5%80%D5%A5%D5%B2%D5%AB%D5%B6%D5%A1%D5%AF%3A%D5%8E%D5%AB%D5%AF%D5%BF%D5%B8%D6%80_%D5%80%D5%B5%D5%B8%D6%82%D5%A3%D5%B8') + should(getSitelinkUrl({ site: 'hywikisource', title: 'Հեղինակ:Վիկտոր Հյուգո' })).equal('https://hy.wikisource.org/wiki/%D5%80%D5%A5%D5%B2%D5%AB%D5%B6%D5%A1%D5%AF%3A%D5%8E%D5%AB%D5%AF%D5%BF%D5%B8%D6%80_%D5%80%D5%B5%D5%B8%D6%82%D5%A3%D5%B8') - getSitelinkUrl({ site: 'zhwikiquote', title: '維克多·雨果' }) - .should.equal('https://zh.wikiquote.org/wiki/%E7%B6%AD%E5%85%8B%E5%A4%9A%C2%B7%E9%9B%A8%E6%9E%9C') + should(getSitelinkUrl({ site: 'zhwikiquote', title: '維克多·雨果' })).equal('https://zh.wikiquote.org/wiki/%E7%B6%AD%E5%85%8B%E5%A4%9A%C2%B7%E9%9B%A8%E6%9E%9C') // Couldn't find a wiktionary sitelink - getSitelinkUrl({ site: 'nlwikibooks', title: 'Programmeren in SPARQL' }) - .should.equal('https://nl.wikibooks.org/wiki/Programmeren_in_SPARQL') + should(getSitelinkUrl({ site: 'nlwikibooks', title: 'Programmeren in SPARQL' })).equal('https://nl.wikibooks.org/wiki/Programmeren_in_SPARQL') - getSitelinkUrl({ site: 'frwikiversity', title: 'SPARQL Protocol and RDF Query Language' }) - .should.equal('https://fr.wikiversity.org/wiki/SPARQL_Protocol_and_RDF_Query_Language') + should(getSitelinkUrl({ site: 'frwikiversity', title: 'SPARQL Protocol and RDF Query Language' })).equal('https://fr.wikiversity.org/wiki/SPARQL_Protocol_and_RDF_Query_Language') - getSitelinkUrl({ site: 'dewikivoyage', title: 'Lyon' }) - .should.equal('https://de.wikivoyage.org/wiki/Lyon') + should(getSitelinkUrl({ site: 'dewikivoyage', title: 'Lyon' })).equal('https://de.wikivoyage.org/wiki/Lyon') - getSitelinkUrl({ site: 'enwikinews', title: 'Category:Lyon' }) - .should.equal('https://en.wikinews.org/wiki/Category%3ALyon') + should(getSitelinkUrl({ site: 'enwikinews', title: 'Category:Lyon' })).equal('https://en.wikinews.org/wiki/Category%3ALyon') - getSitelinkUrl({ site: 'wikidata', title: 'Q1' }) - .should.equal('https://www.wikidata.org/wiki/Q1') + should(getSitelinkUrl({ site: 'wikidata', title: 'Q1' })).equal('https://www.wikidata.org/wiki/Q1') - getSitelinkUrl({ site: 'wikidatawiki', title: 'Q1' }) - .should.equal('https://www.wikidata.org/wiki/Q1') + should(getSitelinkUrl({ site: 'wikidatawiki', title: 'Q1' })).equal('https://www.wikidata.org/wiki/Q1') - getSitelinkUrl({ site: 'wikidata', title: 'P50' }) - .should.equal('https://www.wikidata.org/wiki/Property:P50') + should(getSitelinkUrl({ site: 'wikidata', title: 'P50' })).equal('https://www.wikidata.org/wiki/Property:P50') - getSitelinkUrl({ site: 'wikidatawiki', title: 'L622301' }) - .should.equal('https://www.wikidata.org/wiki/Lexeme:L622301') + should(getSitelinkUrl({ site: 'wikidatawiki', title: 'L622301' })).equal('https://www.wikidata.org/wiki/Lexeme:L622301') - getSitelinkUrl({ site: 'wikidatawiki', title: 'L622301-F1' }) - .should.equal('https://www.wikidata.org/wiki/Lexeme:L622301#F1') + should(getSitelinkUrl({ site: 'wikidatawiki', title: 'L622301-F1' })).equal('https://www.wikidata.org/wiki/Lexeme:L622301#F1') - getSitelinkUrl({ site: 'wikidatawiki', title: 'L16097-S1' }) - .should.equal('https://www.wikidata.org/wiki/Lexeme:L16097#S1') + should(getSitelinkUrl({ site: 'wikidatawiki', title: 'L16097-S1' })).equal('https://www.wikidata.org/wiki/Lexeme:L16097#S1') - getSitelinkUrl({ site: 'wikidatawiki', title: 'E1' }) - .should.equal('https://www.wikidata.org/wiki/EntitySchema:E1') + should(getSitelinkUrl({ site: 'wikidatawiki', title: 'E1' })).equal('https://www.wikidata.org/wiki/EntitySchema:E1') }) it('should accept a sitelink object as unique argument', () => { - getSitelinkUrl({ site: 'commons', title: 'Lyon' }) - .should.equal('https://commons.wikimedia.org/wiki/Lyon') + should(getSitelinkUrl({ site: 'commons', title: 'Lyon' })).equal('https://commons.wikimedia.org/wiki/Lyon') - getSitelinkUrl({ site: 'frwiki', title: 'Septembre' }) - .should.equal('https://fr.wikipedia.org/wiki/Septembre') + should(getSitelinkUrl({ site: 'frwiki', title: 'Septembre' })).equal('https://fr.wikipedia.org/wiki/Septembre') - getSitelinkUrl({ site: 'hywikisource', title: 'Հեղինակ:Վիկտոր Հյուգո' }) - .should.equal('https://hy.wikisource.org/wiki/%D5%80%D5%A5%D5%B2%D5%AB%D5%B6%D5%A1%D5%AF%3A%D5%8E%D5%AB%D5%AF%D5%BF%D5%B8%D6%80_%D5%80%D5%B5%D5%B8%D6%82%D5%A3%D5%B8') + should(getSitelinkUrl({ site: 'hywikisource', title: 'Հեղինակ:Վիկտոր Հյուգո' })).equal('https://hy.wikisource.org/wiki/%D5%80%D5%A5%D5%B2%D5%AB%D5%B6%D5%A1%D5%AF%3A%D5%8E%D5%AB%D5%AF%D5%BF%D5%B8%D6%80_%D5%80%D5%B5%D5%B8%D6%82%D5%A3%D5%B8') - getSitelinkUrl({ site: 'zhwikiquote', title: '維克多·雨果' }) - .should.equal('https://zh.wikiquote.org/wiki/%E7%B6%AD%E5%85%8B%E5%A4%9A%C2%B7%E9%9B%A8%E6%9E%9C') + should(getSitelinkUrl({ site: 'zhwikiquote', title: '維克多·雨果' })).equal('https://zh.wikiquote.org/wiki/%E7%B6%AD%E5%85%8B%E5%A4%9A%C2%B7%E9%9B%A8%E6%9E%9C') // Couldn't find a wiktionary sitelink - getSitelinkUrl({ site: 'nlwikibooks', title: 'Programmeren in SPARQL' }) - .should.equal('https://nl.wikibooks.org/wiki/Programmeren_in_SPARQL') + should(getSitelinkUrl({ site: 'nlwikibooks', title: 'Programmeren in SPARQL' })).equal('https://nl.wikibooks.org/wiki/Programmeren_in_SPARQL') - getSitelinkUrl({ site: 'frwikiversity', title: 'SPARQL Protocol and RDF Query Language' }) - .should.equal('https://fr.wikiversity.org/wiki/SPARQL_Protocol_and_RDF_Query_Language') + should(getSitelinkUrl({ site: 'frwikiversity', title: 'SPARQL Protocol and RDF Query Language' })).equal('https://fr.wikiversity.org/wiki/SPARQL_Protocol_and_RDF_Query_Language') - getSitelinkUrl({ site: 'dewikivoyage', title: 'Lyon' }) - .should.equal('https://de.wikivoyage.org/wiki/Lyon') + should(getSitelinkUrl({ site: 'dewikivoyage', title: 'Lyon' })).equal('https://de.wikivoyage.org/wiki/Lyon') - getSitelinkUrl({ site: 'enwikinews', title: 'Category:Lyon' }) - .should.equal('https://en.wikinews.org/wiki/Category%3ALyon') + should(getSitelinkUrl({ site: 'enwikinews', title: 'Category:Lyon' })).equal('https://en.wikinews.org/wiki/Category%3ALyon') - getSitelinkUrl({ site: 'wikidata', title: 'Q1' }) - .should.equal('https://www.wikidata.org/wiki/Q1') + should(getSitelinkUrl({ site: 'wikidata', title: 'Q1' })).equal('https://www.wikidata.org/wiki/Q1') - getSitelinkUrl({ site: 'wikidatawiki', title: 'Q1' }) - .should.equal('https://www.wikidata.org/wiki/Q1') + should(getSitelinkUrl({ site: 'wikidatawiki', title: 'Q1' })).equal('https://www.wikidata.org/wiki/Q1') - getSitelinkUrl({ site: 'wikidata', title: 'P50' }) - .should.equal('https://www.wikidata.org/wiki/Property:P50') + should(getSitelinkUrl({ site: 'wikidata', title: 'P50' })).equal('https://www.wikidata.org/wiki/Property:P50') }) it('should replace spaces by underscores', () => { - getSitelinkUrl({ site: 'eswikiquote', title: 'Gilles Deleuze' }) - .should.equal('https://es.wikiquote.org/wiki/Gilles_Deleuze') + should(getSitelinkUrl({ site: 'eswikiquote', title: 'Gilles Deleuze' })).equal('https://es.wikiquote.org/wiki/Gilles_Deleuze') }) it('should reject invalid sitelinks', () => { // @ts-expect-error - (() => getSitelinkUrl({ site: 'frperlinpinpin', title: 'Lyon' })).should.throw() + should(() => (getSitelinkUrl({ site: 'frperlinpinpin', title: 'Lyon' }))).throw() // @ts-expect-error - ;(() => getSitelinkUrl({ site: 'frwikiwiki', title: 'Lyon' })).should.throw() + should(() => (getSitelinkUrl({ site: 'frwikiwiki', title: 'Lyon' }))).throw() }) it('should support multi-part language codes', () => { - getSitelinkUrl({ site: 'zh_classicalwiki', title: '編訂名詞館' }) - .should.startWith('https://zh-classical.wikipedia.org/') - getSitelinkUrl({ site: 'be_x_oldwiki', title: 'Віктор_Юго' }) - .should.startWith('https://be-x-old.wikipedia.org/') + should(getSitelinkUrl({ site: 'zh_classicalwiki', title: '編訂名詞館' })).startWith('https://zh-classical.wikipedia.org/') + should(getSitelinkUrl({ site: 'be_x_oldwiki', title: 'Віктор_Юго' })).startWith('https://be-x-old.wikipedia.org/') }) }) describe('getSitelinkData', () => { it('should return a sitelink data from a sitelink key', () => { - getSitelinkData('frwiki').lang.should.equal('fr') - getSitelinkData('frwiki').project.should.equal('wikipedia') - getSitelinkData('dewikiquote').lang.should.equal('de') - getSitelinkData('dewikiquote').project.should.equal('wikiquote') - getSitelinkData('commonswiki').project.should.equal('commons') - getSitelinkData('wikidatawiki').project.should.equal('wikidata') + should(getSitelinkData('frwiki').lang).equal('fr') + should(getSitelinkData('frwiki').project).equal('wikipedia') + should(getSitelinkData('dewikiquote').lang).equal('de') + should(getSitelinkData('dewikiquote').project).equal('wikiquote') + should(getSitelinkData('commonswiki').project).equal('commons') + should(getSitelinkData('wikidatawiki').project).equal('wikidata') // Using 'en' as placeholder - getSitelinkData('wikidatawiki').lang.should.equal('en') - getSitelinkData('commonswiki').lang.should.equal('en') + should(getSitelinkData('wikidatawiki').lang).equal('en') + should(getSitelinkData('commonswiki').lang).equal('en') }) it('should return sitelink data from a URL', () => { - getSitelinkData('https://de.wikipedia.org/wiki/The_Score_(2001)').lang.should.equal('de') - getSitelinkData('https://de.wikipedia.org/wiki/The_Score_(2001)').project.should.equal('wikipedia') - getSitelinkData('https://de.wikipedia.org/wiki/The_Score_(2001)').key.should.equal('dewiki') - getSitelinkData('https://de.wikipedia.org/wiki/The_Score_(2001)').title.should.equal('The_Score_(2001)') - - getSitelinkData('https://www.wikidata.org/wiki/Q4115189').lang.should.equal('en') - getSitelinkData('https://www.wikidata.org/wiki/Q4115189').project.should.equal('wikidata') - getSitelinkData('https://www.wikidata.org/wiki/Q4115189').key.should.equal('wikidata') - getSitelinkData('https://www.wikidata.org/wiki/Q4115189').title.should.equal('Q4115189') - - getSitelinkData('https://commons.wikimedia.org/wiki/Category:ITER').lang.should.equal('en') - getSitelinkData('https://commons.wikimedia.org/wiki/Category:ITER').project.should.equal('commons') - getSitelinkData('https://commons.wikimedia.org/wiki/Category:ITER').key.should.equal('commons') - getSitelinkData('https://commons.wikimedia.org/wiki/Category:ITER').title.should.equal('Category:ITER') + should(getSitelinkData('https://de.wikipedia.org/wiki/The_Score_(2001)').lang).equal('de') + should(getSitelinkData('https://de.wikipedia.org/wiki/The_Score_(2001)').project).equal('wikipedia') + should(getSitelinkData('https://de.wikipedia.org/wiki/The_Score_(2001)').key).equal('dewiki') + should(getSitelinkData('https://de.wikipedia.org/wiki/The_Score_(2001)').title).equal('The_Score_(2001)') + + should(getSitelinkData('https://www.wikidata.org/wiki/Q4115189').lang).equal('en') + should(getSitelinkData('https://www.wikidata.org/wiki/Q4115189').project).equal('wikidata') + should(getSitelinkData('https://www.wikidata.org/wiki/Q4115189').key).equal('wikidata') + should(getSitelinkData('https://www.wikidata.org/wiki/Q4115189').title).equal('Q4115189') + + should(getSitelinkData('https://commons.wikimedia.org/wiki/Category:ITER').lang).equal('en') + should(getSitelinkData('https://commons.wikimedia.org/wiki/Category:ITER').project).equal('commons') + should(getSitelinkData('https://commons.wikimedia.org/wiki/Category:ITER').key).equal('commons') + should(getSitelinkData('https://commons.wikimedia.org/wiki/Category:ITER').title).equal('Category:ITER') }) it('should reject invalid sitelink key', () => { - getSitelinkData.bind(null, 'foowiki').should.throw() + should(() => getSitelinkData('foowiki')).throw() }) it('should parse encoded URL components', () => { - getSitelinkData('https://de.wikipedia.org/wiki/The_Score_%282001%29').title.should.equal('The_Score_(2001)') - getSitelinkData('https://de.wikipedia.org/wiki/The_Score_%282001%29').lang.should.equal('de') - getSitelinkData('https://de.wikipedia.org/wiki/The_Score_%282001%29').project.should.equal('wikipedia') - getSitelinkData('https://de.wikipedia.org/wiki/The_Score_%282001%29').key.should.equal('dewiki') + should(getSitelinkData('https://de.wikipedia.org/wiki/The_Score_%282001%29').title).equal('The_Score_(2001)') + should(getSitelinkData('https://de.wikipedia.org/wiki/The_Score_%282001%29').lang).equal('de') + should(getSitelinkData('https://de.wikipedia.org/wiki/The_Score_%282001%29').project).equal('wikipedia') + should(getSitelinkData('https://de.wikipedia.org/wiki/The_Score_%282001%29').key).equal('dewiki') }) it('should support multi-part language codes', () => { const data = getSitelinkData('https://be-x-old.wikipedia.org/wiki/Беларускі_клясычны_правапіс') - data.title.should.equal('Беларускі_клясычны_правапіс') - data.lang.should.equal('be_x_old') - data.project.should.equal('wikipedia') - data.key.should.equal('be_x_oldwiki') + should(data.title).equal('Беларускі_клясычны_правапіс') + should(data.lang).equal('be_x_old') + should(data.project).equal('wikipedia') + should(data.key).equal('be_x_oldwiki') }) }) describe('isSitelinkKey', () => { it('should return true for valid sitelink keys', () => { - isSitelinkKey('frwiki').should.be.true() - isSitelinkKey('be_x_oldwiki').should.be.true() - isSitelinkKey('commonswiki').should.be.true() - isSitelinkKey('wikidatawiki').should.be.true() - isSitelinkKey('commons').should.be.false() - isSitelinkKey('wikidata').should.be.false() + should(isSitelinkKey('frwiki')).be.true() + should(isSitelinkKey('be_x_oldwiki')).be.true() + should(isSitelinkKey('commonswiki')).be.true() + should(isSitelinkKey('wikidatawiki')).be.true() + should(isSitelinkKey('commons')).be.false() + should(isSitelinkKey('wikidata')).be.false() }) it('should return false for invalid sitelink keys', () => { - isSitelinkKey('frperlinpinpin').should.be.false() - isSitelinkKey('frwikilinpinpin').should.be.false() - isSitelinkKey('imaginarylangwiki').should.be.false() - isSitelinkKey('frwikiwiki').should.be.false() - isSitelinkKey('be-x-oldwiki').should.be.false() + should(isSitelinkKey('frperlinpinpin')).be.false() + should(isSitelinkKey('frwikilinpinpin')).be.false() + should(isSitelinkKey('imaginarylangwiki')).be.false() + should(isSitelinkKey('frwikiwiki')).be.false() + should(isSitelinkKey('be-x-oldwiki')).be.false() }) }) diff --git a/tests/sparql_query.ts b/tests/sparql_query.ts index 546ce60f..aa76f487 100644 --- a/tests/sparql_query.ts +++ b/tests/sparql_query.ts @@ -1,4 +1,4 @@ -import 'should' +import should from 'should' import { sparqlQueryFactory } from '../src/queries/sparql_query.js' import { sparqlEndpoint } from './lib/tests_env.js' @@ -27,12 +27,12 @@ const sparqlExemple = ` describe('sparqlQuery', () => { it('env', () => { - sparqlQuery.should.be.a.Function() + should(sparqlQuery).be.a.Function() }) it('should return a url', () => { const url = sparqlQuery(sparqlExemple) - url.should.be.a.String() - url.should.match(/https:\/\//) + should(url).be.a.String() + should(url).match(/https:\/\//) }) }) diff --git a/tests/time.ts b/tests/time.ts new file mode 100644 index 00000000..516d8396 --- /dev/null +++ b/tests/time.ts @@ -0,0 +1,119 @@ +import should from 'should' +import { wikibaseTimeToEpochTime, wikibaseTimeToISOString, wikibaseTimeToSimpleDay } from '../src/helpers/wikibase_time.js' +import { readJsonFile } from './lib/utils.js' +import type { Item } from '../src/types/entity.js' + +const Q970917 = readJsonFile('./tests/data/Q970917.json') as Item + +describe('time', () => { + const ISOtime = '2014-05-14T00:00:00.000Z' + const wdTime = '+2014-05-14T00:00:00Z' + const epoch = 1400025600000 + const ISOnegativeTime = '-000044-03-15T00:00:00.000Z' + const negativeWdTime = '-0044-03-15T00:00:00Z' + const negativeEpoch = -63549360000000 + + describe('wikibaseTimeToEpochTime', () => { + it('env', () => { + should(new Date(epoch).toISOString()).equal(ISOtime) + should(new Date(negativeEpoch).toISOString()).equal(ISOnegativeTime) + }) + + it('should return a number (epoch time)', () => { + should(wikibaseTimeToEpochTime(wdTime)).be.a.Number() + }) + + it('should return a number for negative time', () => { + should(wikibaseTimeToEpochTime(negativeWdTime)).be.a.Number() + }) + + it('should return the right number', () => { + should(wikibaseTimeToEpochTime(wdTime)).equal(epoch) + }) + + it('should return the right number for negative time too', () => { + should(wikibaseTimeToEpochTime(negativeWdTime)).equal(negativeEpoch) + }) + + it('should accept a value object', () => { + // @ts-expect-error + should(wikibaseTimeToEpochTime(Q970917.claims.P569[0].mainsnak.datavalue.value)).equal(-3160944000000) + // @ts-expect-error + should(wikibaseTimeToEpochTime(Q970917.claims.P569[1].mainsnak.datavalue.value)).equal(657417600000) + // @ts-expect-error + should(wikibaseTimeToEpochTime(Q970917.claims.P569[2].mainsnak.datavalue.value)).equal(631152000000) + }) + }) + + describe('wikibaseTimeToISOString', () => { + it('should convert wikibase date to ISO date', () => { + should(wikibaseTimeToISOString('+1885-05-22T00:00:00Z')).equal('1885-05-22T00:00:00.000Z') + should(wikibaseTimeToISOString('+0180-03-17T00:00:00Z')).equal('0180-03-17T00:00:00.000Z') + should(wikibaseTimeToISOString('-0398-00-00T00:00:00Z')).equal('-000398-01-01T00:00:00.000Z') + should(wikibaseTimeToISOString('-34000-00-00T00:00:00Z')).equal('-034000-01-01T00:00:00.000Z') + should(wikibaseTimeToISOString('+34000-00-00T00:00:00Z')).equal('+034000-01-01T00:00:00.000Z') + }) + + it('should return a valid time for possible invalid dates', () => { + should(wikibaseTimeToISOString('+1953-00-00T00:00:00Z')).equal('1953-01-01T00:00:00.000Z') + should(wikibaseTimeToISOString('+1953-11-00T00:00:00Z')).equal('1953-11-01T00:00:00.000Z') + }) + + it('should return a valid time even for possible invalid negative date', () => { + should(wikibaseTimeToISOString('-1953-00-00T00:00:00Z')).equal('-001953-01-01T00:00:00.000Z') + should(wikibaseTimeToISOString('-1953-11-00T00:00:00Z')).equal('-001953-11-01T00:00:00.000Z') + }) + + it('should return a valid time for dates far in the past', () => { + should(wikibaseTimeToISOString('-13798000000-00-00T00:00:00Z')).equal('-13798000000-01-01T00:00:00Z') + should(wikibaseTimeToISOString('-13798000000-02-00T00:00:00Z')).equal('-13798000000-02-01T00:00:00Z') + should(wikibaseTimeToISOString('-13798000000-02-07T15:00:00Z')).equal('-13798000000-02-07T15:00:00Z') + }) + + it('should return a valid time for dates far in the future', () => { + should(wikibaseTimeToISOString('+13798000000-00-00T00:00:00Z')).equal('+13798000000-01-01T00:00:00Z') + should(wikibaseTimeToISOString('+13798000000-02-00T00:00:00Z')).equal('+13798000000-02-01T00:00:00Z') + should(wikibaseTimeToISOString('+13798000000-02-07T15:00:00Z')).equal('+13798000000-02-07T15:00:00Z') + }) + + it('should accept a value object', () => { + // @ts-expect-error + should(wikibaseTimeToISOString(Q970917.claims.P569[0].mainsnak.datavalue.value)).equal('1869-11-01T00:00:00.000Z') + // @ts-expect-error + should(wikibaseTimeToISOString(Q970917.claims.P569[1].mainsnak.datavalue.value)).equal('1990-11-01T00:00:00.000Z') + // @ts-expect-error + should(wikibaseTimeToISOString(Q970917.claims.P569[2].mainsnak.datavalue.value)).equal('1990-01-01T00:00:00.000Z') + }) + }) + + describe('wikibaseTimeToSimpleDay', () => { + it('should convert wikibase date with year precision to simple-day', () => { + should(wikibaseTimeToSimpleDay('+1953-00-00T00:00:00Z')).equal('1953') + should(wikibaseTimeToSimpleDay('-1953-00-00T00:00:00Z')).equal('-1953') + should(wikibaseTimeToSimpleDay('+13-00-00T00:00:00Z')).equal('13') + should(wikibaseTimeToSimpleDay('-13-00-00T00:00:00Z')).equal('-13') + should(wikibaseTimeToSimpleDay('-0100-00-00T00:00:00Z')).equal('-100') + }) + + it('should convert wikibase date with month precision to simple-day', () => { + should(wikibaseTimeToSimpleDay('+1953-01-00T00:00:00Z')).equal('1953-01') + should(wikibaseTimeToSimpleDay('-1953-01-00T00:00:00Z')).equal('-1953-01') + should(wikibaseTimeToSimpleDay('+13-01-00T00:00:00Z')).equal('13-01') + should(wikibaseTimeToSimpleDay('-13-01-00T00:00:00Z')).equal('-13-01') + should(wikibaseTimeToSimpleDay('-0044-03-00T00:00:00Z')).equal('-44-03') + }) + + it('should convert wikibase date with day precision or finer to simple-day', () => { + should(wikibaseTimeToSimpleDay('+1953-01-01T00:00:00Z')).equal('1953-01-01') + should(wikibaseTimeToSimpleDay('-1953-01-01T00:00:00Z')).equal('-1953-01-01') + should(wikibaseTimeToSimpleDay('+1953-01-01T13:45:00Z')).equal('1953-01-01') + should(wikibaseTimeToSimpleDay('-1953-01-01T13:45:00Z')).equal('-1953-01-01') + should(wikibaseTimeToSimpleDay('-0044-03-01T00:00:00Z')).equal('-44-03-01') + }) + + it('should accept a value object', () => { + // @ts-expect-error + should(wikibaseTimeToSimpleDay(Q970917.claims.P569[0].mainsnak.datavalue.value)).equal('1869-11') + }) + }) +}) diff --git a/tests/utils.ts b/tests/utils.ts index 9d8ed4aa..151c5d8a 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,29 +1,29 @@ -import 'should' +import should from 'should' import { isPlainObject } from '../src/utils/utils.js' describe('utils', () => { describe('isPlainObject', () => { it('should return true for plain objects', () => { - isPlainObject({}).should.equal(true) - isPlainObject({ a: 1, b: 2 }).should.equal(true) + should(isPlainObject({})).be.true() + should(isPlainObject({ a: 1, b: 2 })).be.true() }) it('should return false for arrays', () => { - isPlainObject([]).should.equal(false) - isPlainObject([ 1, 2 ]).should.equal(false) + should(isPlainObject([])).be.false() + should(isPlainObject([ 1, 2 ])).be.false() }) it('should return false for strings', () => { - isPlainObject('').should.equal(false) - isPlainObject('hello').should.equal(false) + should(isPlainObject('')).be.false() + should(isPlainObject('hello')).be.false() }) it('should return false for null', () => { - isPlainObject(null).should.equal(false) + should(isPlainObject(null)).be.false() }) it('should return false for undefined', () => { - isPlainObject(undefined).should.equal(false) + should(isPlainObject(undefined)).be.false() }) }) }) diff --git a/tsconfig.json b/tsconfig.json index 4714d35c..43d6d7ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -23,8 +23,18 @@ "noUnusedLocals": true, "noUnusedParameters": true, // "strict": true, + + // part of strict + "alwaysStrict": true, + // "noImplicitAny": true, + "noImplicitThis": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + // "strictNullChecks": true, + // "strictPropertyInitialization": true, + "useUnknownInCatchVariables": true, }, - "include": ["src"], + "include": ["src", "tests", "scripts"], "ts-node": { "esm": true, } From c74eac5a98de2f73cfe51635df498a3b375af72e Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Fri, 24 Feb 2023 14:36:20 +0100 Subject: [PATCH 2/6] chore(gitignore): ignore npm pack output also remove old unused ignores --- .gitignore | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index c7948aec..89a9e064 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ +/*-*.*.*.tgz +dist node_modules repo-backup -scripts/sitelinks_languages/sites.json -types/**/*.js -dist From 072ec4eaea02c5ca6cd938781d5bfebba2d10344 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Fri, 24 Feb 2023 14:37:34 +0100 Subject: [PATCH 3/6] build(npm): only include dist/src reduce package size and only include whats needed --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index a25582c7..e88c3e36 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,7 @@ }, "types": "./dist/src/index.d.ts", "files": [ - "src", - "dist" + "dist/src" ], "directories": { "lib": "dist", From 5fa1e50d3d11c0f5e69b921f36d33146a96b77b6 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Mon, 27 Feb 2023 13:30:43 +0100 Subject: [PATCH 4/6] refactor: use eslint-config-standard-with-typescript Also removes lint definitions that are exactly as the config. --- .eslintrc.cjs | 26 +++++++++---------- package-lock.json | 30 +++++++++++++++++++++- package.json | 2 +- src/helpers/simplify_sparql_results.ts | 2 +- src/helpers/simplify_text_attributes.ts | 2 +- src/helpers/validate.ts | 2 +- src/queries/cirrus_search.ts | 10 ++++---- src/queries/get_entities.ts | 4 ++- src/queries/get_entities_from_sitelinks.ts | 2 ++ src/queries/get_reverse_claims.ts | 2 +- src/queries/get_revisions.ts | 2 +- src/queries/search_entities.ts | 2 +- src/types/entity.ts | 26 +++++++++---------- src/types/simplify_claims.ts | 8 ++---- src/types/sparql.ts | 4 +-- src/types/terms.ts | 2 +- src/types/wbgetentities.ts | 21 ++++++++------- src/wikibase-sdk.ts | 6 ++--- tests/get_many_entities.ts | 4 +-- 19 files changed, 94 insertions(+), 63 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1f291800..a95b0048 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -11,16 +11,18 @@ module.exports = { plugins: [ '@typescript-eslint', ], + parserOptions: { + project: './tsconfig.json' + }, extends: [ // See https://github.com/standard/eslint-config-standard/blob/master/eslintrc.json - 'standard', + 'standard-with-typescript', 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended', ], rules: { 'array-bracket-spacing': [ 'error', 'always' ], 'arrow-parens': [ 'error', 'as-needed' ], - 'comma-dangle': 'off', '@typescript-eslint/comma-dangle': [ 'error', 'always-multiline' ], eqeqeq: [ 'error', 'smart' ], 'implicit-arrow-linebreak': [ 'error', 'beside' ], @@ -36,28 +38,26 @@ module.exports = { alphabetize: { order: 'asc' }, }, ], - 'indent': 'off', '@typescript-eslint/indent': [ 'error', 2, { MemberExpression: 'off' } ], 'no-var': [ 'error' ], - // Primarily to avoid false positive with interfaces declarations - // See https://github.com/typescript-eslint/typescript-eslint/issues/1262 - 'no-use-before-define': 'off', - '@typescript-eslint/no-use-before-define': 'off', 'nonblock-statement-body-position': [ 'error', 'beside' ], - 'object-curly-spacing': 'off', - '@typescript-eslint/object-curly-spacing': [ 'error', 'always' ], 'object-shorthand': [ 'error', 'properties' ], 'prefer-arrow-callback': [ 'error' ], - 'prefer-const': [ 'error' ], 'prefer-rest-params': 'off', + + // TODO remove (= enable these lints and fix the issues) '@typescript-eslint/ban-ts-comment': [ 'error', { 'ts-expect-error': false, // TODO: "allow-with-description", 'ts-nocheck': false, } ], - '@typescript-eslint/consistent-type-imports': [ 'error', { prefer: 'type-imports' } ], + '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-explicit-any': 'off', - semi: 'off', - '@typescript-eslint/semi': [ 'error', 'never' ], + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + + // Enable when strict or strictNullChecks is enabled in tsconfig + '@typescript-eslint/prefer-nullish-coalescing': 'off', + '@typescript-eslint/strict-boolean-expressions': 'off', }, globals: { // Mocha globals diff --git a/package-lock.json b/package-lock.json index 41a18c09..f22f3ca6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@typescript-eslint/parser": "^5.49.0", "@vercel/git-hooks": "^1.0.0", "eslint": "^8.32.0", - "eslint-config-standard": "^17.0.0", + "eslint-config-standard-with-typescript": "^34.0.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", @@ -1189,6 +1189,24 @@ "eslint-plugin-promise": "^6.0.0" } }, + "node_modules/eslint-config-standard-with-typescript": { + "version": "34.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-34.0.0.tgz", + "integrity": "sha512-zhCsI4/A0rJ1ma8sf3RLXYc0gc7yPmdTWRVXMh9dtqeUx3yBQyALH0wosHhk1uQ9QyItynLdNOtcHKNw8G7lQw==", + "dev": true, + "dependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint-config-standard": "17.0.0" + }, + "peerDependencies": { + "@typescript-eslint/eslint-plugin": "^5.0.0", + "eslint": "^8.0.1", + "eslint-plugin-import": "^2.25.2", + "eslint-plugin-n": "^15.0.0", + "eslint-plugin-promise": "^6.0.0", + "typescript": "*" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", @@ -4750,6 +4768,16 @@ "dev": true, "requires": {} }, + "eslint-config-standard-with-typescript": { + "version": "34.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-34.0.0.tgz", + "integrity": "sha512-zhCsI4/A0rJ1ma8sf3RLXYc0gc7yPmdTWRVXMh9dtqeUx3yBQyALH0wosHhk1uQ9QyItynLdNOtcHKNw8G7lQw==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^5.0.0", + "eslint-config-standard": "17.0.0" + } + }, "eslint-import-resolver-node": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", diff --git a/package.json b/package.json index e88c3e36..047c6390 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@typescript-eslint/parser": "^5.49.0", "@vercel/git-hooks": "^1.0.0", "eslint": "^8.32.0", - "eslint-config-standard": "^17.0.0", + "eslint-config-standard-with-typescript": "^34.0.0", "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.6.1", "eslint-plugin-promise": "^6.1.1", diff --git a/src/helpers/simplify_sparql_results.ts b/src/helpers/simplify_sparql_results.ts index 2cc2228a..35c2dea0 100644 --- a/src/helpers/simplify_sparql_results.ts +++ b/src/helpers/simplify_sparql_results.ts @@ -1,6 +1,6 @@ import type { SimplifiedSparqlResultMinified, SimplifiedSparqlResultRecord, SimplifiedSparqlResults, SparqlResults, SparqlValueObj, SparqlValueRaw, SparqlValueType } from '../types/sparql.js' -export type SimplifySparqlResultsOptions = { +export interface SimplifySparqlResultsOptions { readonly minimize?: boolean } diff --git a/src/helpers/simplify_text_attributes.ts b/src/helpers/simplify_text_attributes.ts index bf670664..75b44df1 100644 --- a/src/helpers/simplify_text_attributes.ts +++ b/src/helpers/simplify_text_attributes.ts @@ -2,7 +2,7 @@ import { typedEntries } from '../utils/utils.js' import type { WmLanguageCode } from '../types/options.js' import type { Aliases, Descriptions, Glosses, Labels, Lemmas, Representations, SimplifiedAliases, SimplifiedDescriptions, SimplifiedGlosses, SimplifiedLabels, SimplifiedLemmas, SimplifiedRepresentations } from '../types/terms.js' -type InValue = { readonly value: T } +interface InValue { readonly value: T } function singleValue (data: Partial>>>) { const simplified: Partial> = {} diff --git a/src/helpers/validate.ts b/src/helpers/validate.ts index 50ad95eb..d9b6865f 100644 --- a/src/helpers/validate.ts +++ b/src/helpers/validate.ts @@ -3,7 +3,7 @@ import { isEntityId, isEntityPageTitle, isPropertyId, isRevisionId } from './hel /** Ensure both via TypeScript and at runtime that the input value is of the expected type. Throws error when it is not */ function validate (name: string, testFn: (value: string) => value is T) { return function (value: T): void { - if (!testFn(value)) throw new Error(`invalid ${name}: ${value}`) + if (!testFn(value)) throw new Error(`invalid ${name}: ${String(value)}`) } } diff --git a/src/queries/cirrus_search.ts b/src/queries/cirrus_search.ts index eb869899..a798a49b 100644 --- a/src/queries/cirrus_search.ts +++ b/src/queries/cirrus_search.ts @@ -64,20 +64,20 @@ export function cirrusSearchPagesFactory (buildUrl: BuildUrlFunction) { } if (profile != null && typeof profile !== 'string') { - throw new Error(`invalid profile: ${profile} (${typeof profile}, expected string)`) + throw new Error(`invalid profile: ${String(profile)} (${typeof profile}, expected string)`) } if (sort != null && typeof sort !== 'string') { - throw new Error(`invalid sort: ${sort} (${typeof sort}, expected string)`) + throw new Error(`invalid sort: ${String(sort)} (${typeof sort}, expected string)`) } - let srprop + let srprop: string if (prop != null) { if (prop instanceof Array) prop = prop.join('|') if (typeof prop !== 'string') { - throw new Error(`invalid prop: ${prop} (${typeof prop}, expected string)`) + throw new Error(`invalid prop: ${String(prop)} (${typeof prop}, expected string)`) } - srprop = prop.toString() + srprop = prop } return buildUrl({ diff --git a/src/queries/get_entities.ts b/src/queries/get_entities.ts index fd3b5b04..45268346 100644 --- a/src/queries/get_entities.ts +++ b/src/queries/get_entities.ts @@ -29,7 +29,7 @@ export function getEntitiesFactory (buildUrl: BuildUrlFunction) { // Allow to pass ids as a single string ids = forceArray(ids) - ids.forEach(o => validate.entityId(o)) + ids.forEach(o => { validate.entityId(o) }) if (ids.length > 50) { console.warn(`getEntities accepts 50 ids max to match Wikidata API limitations: @@ -48,6 +48,8 @@ export function getEntitiesFactory (buildUrl: BuildUrlFunction) { format, } + // Only specify 'no' when explictly false. Eslint ignores the fact that redirects could be undefined too. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare if (redirects === false) query.redirects = 'no' if (languages) { diff --git a/src/queries/get_entities_from_sitelinks.ts b/src/queries/get_entities_from_sitelinks.ts index 4b676ac4..a39afa1d 100644 --- a/src/queries/get_entities_from_sitelinks.ts +++ b/src/queries/get_entities_from_sitelinks.ts @@ -58,6 +58,8 @@ export function getEntitiesFromSitelinksFactory (buildUrl: BuildUrlFunction) { query.props = props.join('|') } + // Only specify 'no' when explictly false. Eslint ignores the fact that redirects could be undefined too. + // eslint-disable-next-line @typescript-eslint/no-unnecessary-boolean-literal-compare if (redirects === false) query.redirects = 'no' return buildUrl(query) diff --git a/src/queries/get_reverse_claims.ts b/src/queries/get_reverse_claims.ts index ff03c820..7db395d2 100644 --- a/src/queries/get_reverse_claims.ts +++ b/src/queries/get_reverse_claims.ts @@ -31,7 +31,7 @@ export const getReverseClaimsFactory = (sparqlEndpoint: Url) => { // Allow to request values for several properties at once properties = forceArray(properties) - properties.forEach(o => validate.propertyId(o)) + properties.forEach(o => { validate.propertyId(o) }) const valueBlock = getValueBlock(values, valueFn, properties, filter) let sparql = `SELECT DISTINCT ?subject WHERE { ${valueBlock} }` diff --git a/src/queries/get_revisions.ts b/src/queries/get_revisions.ts index 341cbabd..eac75527 100644 --- a/src/queries/get_revisions.ts +++ b/src/queries/get_revisions.ts @@ -22,7 +22,7 @@ export function getRevisionsFactory (buildUrl: BuildUrlFunction) { return function getRevisions ({ ids, format, limit, start, end, prop, user, excludeuser, tag }: GetRevisionsOptions) { rejectObsoleteInterface(arguments) ids = forceArray(ids) - ids.forEach(o => validate.entityPageTitle(o)) + ids.forEach(o => { validate.entityPageTitle(o) }) const uniqueId = ids.length === 1 const query: ApiQueryParameters = { diff --git a/src/queries/search_entities.ts b/src/queries/search_entities.ts index dcfc3f85..ea573880 100644 --- a/src/queries/search_entities.ts +++ b/src/queries/search_entities.ts @@ -28,7 +28,7 @@ export const searchEntitiesFactory = (buildUrl: BuildUrlFunction) => { uselang = uselang || language if (!(search && search.length > 0)) throw new Error("search can't be empty") - if (!isOfType(EntityTypes, type)) throw new Error(`invalid type: ${type}`) + if (!isOfType(EntityTypes, type)) throw new Error(`invalid type: ${String(type)}`) return buildUrl({ action: 'wbsearchentities', diff --git a/src/types/entity.ts b/src/types/entity.ts index 179c0f07..6a7be619 100644 --- a/src/types/entity.ts +++ b/src/types/entity.ts @@ -31,8 +31,8 @@ export type EntityPageTitle = NamespacedEntityId | ItemId export type Entities = Record export interface Property extends EntityInfo { - id: PropertyId, - type: 'property', + id: PropertyId + type: 'property' datatype?: DataType labels?: Labels descriptions?: Descriptions @@ -41,8 +41,8 @@ export interface Property extends EntityInfo { } export interface Item extends EntityInfo { - id: ItemId, - type: 'item', + id: ItemId + type: 'item' labels?: Labels descriptions?: Descriptions aliases?: Aliases @@ -51,8 +51,8 @@ export interface Item extends EntityInfo { } export interface Lexeme extends EntityInfo { - id: LexemeId, - type: 'lexeme', + id: LexemeId + type: 'lexeme' lexicalCategory: ItemId language: ItemId lemmas?: Lemmas @@ -75,8 +75,8 @@ export interface SimplifiedEntityInfo { } export interface SimplifiedItem extends SimplifiedEntityInfo { - id: ItemId, - type: 'item', + id: ItemId + type: 'item' labels?: SimplifiedLabels descriptions?: SimplifiedDescriptions aliases?: SimplifiedAliases @@ -85,9 +85,9 @@ export interface SimplifiedItem extends SimplifiedEntityInfo { } export interface SimplifiedProperty extends SimplifiedEntityInfo { - id: PropertyId, - type: 'property', - datatype: DataType, + id: PropertyId + type: 'property' + datatype: DataType labels?: SimplifiedLabels descriptions?: SimplifiedDescriptions aliases?: SimplifiedAliases @@ -95,8 +95,8 @@ export interface SimplifiedProperty extends SimplifiedEntityInfo { } export interface SimplifiedLexeme extends SimplifiedEntityInfo { - id: LexemeId, - type: 'lexeme', + id: LexemeId + type: 'lexeme' lexicalCategory: ItemId language: ItemId lemmas?: SimplifiedLemmas diff --git a/src/types/simplify_claims.ts b/src/types/simplify_claims.ts index a3c12cdc..fc9c3968 100644 --- a/src/types/simplify_claims.ts +++ b/src/types/simplify_claims.ts @@ -40,14 +40,10 @@ export type SimplifiedSnaks = Record export type SimplifiedQualifier = SimplifiedSnak export type SimplifiedPropertyQualifiers = SimplifiedQualifier[] -export interface SimplifiedQualifiers { - [property: string]: SimplifiedPropertyQualifiers -} +export type SimplifiedQualifiers = Record export type SimplifiedReferenceSnak = SimplifiedSnak -export interface SimplifiedReference { - [property: string]: SimplifiedReferenceSnak -} +export type SimplifiedReference = Record export type SimplifiedReferences = SimplifiedReference[] export type SimplifiedSnak = string | number | CustomSimplifiedSnak diff --git a/src/types/sparql.ts b/src/types/sparql.ts index e77e597c..262dff87 100644 --- a/src/types/sparql.ts +++ b/src/types/sparql.ts @@ -12,11 +12,11 @@ export interface SparqlResults { vars: string[] } results: { - bindings: Record[] + bindings: Array> } } -export type SimplifiedSparqlResultRecord = Record[] +export type SimplifiedSparqlResultRecord = Array> export type SimplifiedSparqlResultMinified = SparqlValueRaw[] export type SimplifiedSparqlResults = SimplifiedSparqlResultRecord | SimplifiedSparqlResultMinified diff --git a/src/types/terms.ts b/src/types/terms.ts index c2d8bf82..a36516fd 100644 --- a/src/types/terms.ts +++ b/src/types/terms.ts @@ -2,7 +2,7 @@ import type { WmLanguageCode } from './options.js' type WmLanguageRecord = Readonly>> -export type Term = { +export interface Term { readonly language: WmLanguageCode readonly value: string } diff --git a/src/types/wbgetentities.ts b/src/types/wbgetentities.ts index 7c87c95c..e20cff97 100644 --- a/src/types/wbgetentities.ts +++ b/src/types/wbgetentities.ts @@ -1,13 +1,16 @@ import type { UrlResultFormat } from './options.js' +// TODO: not sure why this fails as an interface +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type WbGetEntities = { - action: 'wbgetentities', - titles?: string, - sites?: string, - ids?: string, - format: UrlResultFormat, - normalize?: true, - languages?: string, - props?: string, - redirects?: 'yes' | 'no', // Default: yes + action: 'wbgetentities' + titles?: string + sites?: string + ids?: string + format: UrlResultFormat + normalize?: true + languages?: string + props?: string + /** Default: yes */ + redirects?: 'yes' | 'no' } diff --git a/src/wikibase-sdk.ts b/src/wikibase-sdk.ts index 34078804..119b7133 100644 --- a/src/wikibase-sdk.ts +++ b/src/wikibase-sdk.ts @@ -30,7 +30,7 @@ const common = { ...timeHelpers, } as const -type ApiQueries = { +interface ApiQueries { readonly searchEntities: ReturnType readonly cirrusSearchPages: ReturnType readonly getEntities: ReturnType @@ -39,11 +39,11 @@ type ApiQueries = { readonly getEntityRevision: ReturnType readonly getEntitiesFromSitelinks: ReturnType } -type SparqlQueries = { +interface SparqlQueries { readonly sparqlQuery: ReturnType readonly getReverseClaims: ReturnType } -type Instance = { +interface Instance { readonly root: Url readonly apiEndpoint: Url } diff --git a/tests/get_many_entities.ts b/tests/get_many_entities.ts index d66aa8f4..20bdf2f1 100644 --- a/tests/get_many_entities.ts +++ b/tests/get_many_entities.ts @@ -6,7 +6,7 @@ import { parseUrlQuery } from './lib/utils.js' import type { ItemId } from '../src/types/entity.js' const getManyEntities = getManyEntitiesFactory(buildUrl) -const manyIds = range(1, 80).map(id => `Q${id}` as ItemId) +const manyIds = range(1, 80).map(id => `Q${id}`) describe('wikidata getManyEntities', () => { describe('general', () => { @@ -61,7 +61,7 @@ describe('wikidata getManyEntities', () => { it('should add a redirects parameter if false', () => { const urls = getManyEntities({ ids: [ 'Q535' ], redirects: false }) - const url = urls[0] as string + const url = urls[0] should(parseUrlQuery(url).redirects).equal('no') }) }) From 746a6bad0556290e07df408faa5ad5d80c6c2ab9 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Mon, 27 Feb 2023 14:14:23 +0100 Subject: [PATCH 5/6] refactor: allow for custom ApiQueryParameters --- src/queries/get_revisions.ts | 4 ++-- src/types/options.ts | 2 -- src/types/wbgetentities.ts | 4 +--- src/utils/build_url.ts | 13 ++++++++----- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/queries/get_revisions.ts b/src/queries/get_revisions.ts index eac75527..54ca1124 100644 --- a/src/queries/get_revisions.ts +++ b/src/queries/get_revisions.ts @@ -1,8 +1,8 @@ import * as validate from '../helpers/validate.js' import { forceArray, rejectObsoleteInterface } from '../utils/utils.js' import type { EntityPageTitle } from '../types/entity.js' -import type { ApiQueryParameters, UrlResultFormat } from '../types/options.js' -import type { BuildUrlFunction } from '../utils/build_url.js' +import type { UrlResultFormat } from '../types/options.js' +import type { ApiQueryParameters, BuildUrlFunction } from '../utils/build_url.js' // See https://www.wikidata.org/w/api.php?action=help&modules=query+revisions diff --git a/src/types/options.ts b/src/types/options.ts index 66667f81..c328e88b 100644 --- a/src/types/options.ts +++ b/src/types/options.ts @@ -11,8 +11,6 @@ export type Props = 'info' | 'sitelinks' | 'sitelinks/urls' | 'aliases' | 'label export type UrlResultFormat = 'xml' | 'json' export type WmLanguageCode = typeof languages[number] -export type ApiQueryParameters = Record - // export type Url = `http${string}` export type Url = string diff --git a/src/types/wbgetentities.ts b/src/types/wbgetentities.ts index e20cff97..12a09087 100644 --- a/src/types/wbgetentities.ts +++ b/src/types/wbgetentities.ts @@ -1,8 +1,6 @@ import type { UrlResultFormat } from './options.js' -// TODO: not sure why this fails as an interface -// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -export type WbGetEntities = { +export interface WbGetEntities { action: 'wbgetentities' titles?: string sites?: string diff --git a/src/utils/build_url.ts b/src/utils/build_url.ts index f3f0f3e6..d57f540e 100644 --- a/src/utils/build_url.ts +++ b/src/utils/build_url.ts @@ -1,12 +1,17 @@ -import type { ApiQueryParameters, Url } from '../types/options.js' +import type { Url } from '../types/options.js' const isBrowser = typeof location !== 'undefined' && typeof document !== 'undefined' +type ApiQueryValue = string | number | true +export type ApiQueryParameters = Record + +export type BuildUrlFunction = (options: Readonly>>) => Url + export function buildUrlFactory (instanceApiEndpoint: Url): BuildUrlFunction { - return function (queryObj: ApiQueryParameters): Url { + return queryObj => { // Request CORS headers if the request is made from a browser // See https://www.wikidata.org/w/api.php ('origin' parameter) - if (isBrowser) queryObj.origin = '*' + if (isBrowser) queryObj = { ...queryObj, origin: '*' } const queryEntries = Object.entries(queryObj) // Remove null or undefined parameters @@ -16,5 +21,3 @@ export function buildUrlFactory (instanceApiEndpoint: Url): BuildUrlFunction { return instanceApiEndpoint + '?' + query } } - -export type BuildUrlFunction = (options: ApiQueryParameters) => Url From 5b9e26eb2bfc918b80bd4b114949da9a6db79410 Mon Sep 17 00:00:00 2001 From: EdJoPaTo Date: Mon, 27 Feb 2023 19:39:59 +0100 Subject: [PATCH 6/6] refactor: improve parse claim types --- scripts/compare_datatypes.ts | 7 +- src/helpers/parse_claim.ts | 140 ++++++++++++++++++++++----------- src/helpers/simplify_claims.ts | 99 ++++++++++++----------- src/types/claim.ts | 74 ++++++----------- src/types/simplify_claims.ts | 13 +-- src/types/snakvalue.ts | 68 ++++++++++++++++ 6 files changed, 250 insertions(+), 151 deletions(-) create mode 100644 src/types/snakvalue.ts diff --git a/scripts/compare_datatypes.ts b/scripts/compare_datatypes.ts index 3f6927a5..0980f63b 100755 --- a/scripts/compare_datatypes.ts +++ b/scripts/compare_datatypes.ts @@ -1,11 +1,10 @@ #!/usr/bin/env ts-node import { kebabCase } from 'lodash-es' import { red, green } from 'tiny-chalk' -import { parsers } from '../src/helpers/parse_claim.js' +import { DataTypes } from '../src/types/claim.js' +import { isOfType } from '../src/utils/utils.js' import { readJsonFile } from '../tests/lib/utils.js' -const supportedTypes = Object.keys(parsers) - const allDatatypes = readJsonFile('/tmp/all_wikidata_datatypes.json') as string[] allDatatypes .map(typeUri => { @@ -15,7 +14,7 @@ allDatatypes return kebabCase(typeName) }) .forEach(type => { - if (supportedTypes.includes(type)) { + if (isOfType(DataTypes, type)) { console.log(green('ok'), type) } else { console.error(red('unsupported type'), type) diff --git a/src/helpers/parse_claim.ts b/src/helpers/parse_claim.ts index 2794ed64..ee845cd5 100644 --- a/src/helpers/parse_claim.ts +++ b/src/helpers/parse_claim.ts @@ -1,13 +1,18 @@ -import { convertTime } from './wikibase_time.js' -import type { SimplifySnakOptions } from '../types/simplify_claims.js' +import { convertTime, type TimeConverter, type TimeConverterFn } from './wikibase_time.js' +import type { DataType } from '../types/claim.js' +import type { SnakEntityValue, SnakGlobeCoordinateValue, SnakMonolingualTextValue, SnakQuantityValue, SnakStringValue, SnakTimeValue, SnakValue } from '../types/snakvalue.js' -const simple = datavalue => datavalue.value +const simple = (datavalue: { readonly value: T }): T => datavalue.value -const monolingualtext = (datavalue, options) => { +const monolingualtext = (datavalue: SnakMonolingualTextValue, options: { readonly keepRichValues?: boolean } = {}) => { return options.keepRichValues ? datavalue.value : datavalue.value.text } -const entity = (datavalue, options) => prefixedId(datavalue, options.entityPrefix) +interface SimplifyEntitySnakOptions { + readonly entityPrefix?: string +} + +const entity = (datavalue: SnakEntityValue, options: SimplifyEntitySnakOptions = {}) => prefixedId(datavalue, options.entityPrefix) const entityLetter = { item: 'Q', @@ -15,17 +20,24 @@ const entityLetter = { property: 'P', } as const -const prefixedId = (datavalue, prefix) => { +const prefixedId = (datavalue: SnakEntityValue, prefix: string | undefined) => { const { value } = datavalue - const id = value.id || entityLetter[value['entity-type']] + value['numeric-id'] + const id = 'id' in value ? value.id : (entityLetter[value['entity-type']] + value['numeric-id']) return typeof prefix === 'string' ? `${prefix}:${id}` : id } -const quantity = (datavalue, options) => { +interface SimplifiedQuantity { + amount: number + unit: string + upperBound?: number + lowerBound?: number +} + +const quantity = (datavalue: SnakQuantityValue, options: { readonly keepRichValues?: boolean } = {}) => { const { value } = datavalue const amount = parseFloat(value.amount) if (options.keepRichValues) { - const richValue: any = { + const richValue: SimplifiedQuantity = { amount: parseFloat(value.amount), // ex: http://www.wikidata.org/entity/ unit: value.unit.replace(/^https?:\/\/.*\/entity\//, ''), @@ -38,7 +50,7 @@ const quantity = (datavalue, options) => { } } -const coordinate = (datavalue, options) => { +const coordinate = (datavalue: SnakGlobeCoordinateValue, options: { readonly keepRichValues?: boolean } = {}) => { if (options.keepRichValues) { return datavalue.value } else { @@ -46,51 +58,85 @@ const coordinate = (datavalue, options) => { } } -const time = (datavalue, options: SimplifySnakOptions) => { +interface SimplifyTimeSnakOptions { + readonly keepRichValues?: boolean + readonly timeConverter?: TimeConverterFn | TimeConverter +} +const time = (datavalue: SnakTimeValue, options: SimplifyTimeSnakOptions = {}) => { const timeValue = convertTime(options.timeConverter, datavalue.value) if (options.keepRichValues) { - const { timezone, before, after, precision, calendarmodel } = datavalue.value - return { time: timeValue, timezone, before, after, precision, calendarmodel } + return { ...datavalue.value, time: timeValue } } else { return timeValue } } -export const parsers = { - commonsMedia: simple, - 'external-id': simple, - 'geo-shape': simple, - 'globe-coordinate': coordinate, - math: simple, - monolingualtext, - 'musical-notation': simple, - quantity, - string: simple, - 'tabular-data': simple, - time, - url: simple, - 'wikibase-entityid': entity, - 'wikibase-form': entity, - 'wikibase-item': entity, - 'wikibase-lexeme': entity, - 'wikibase-property': entity, - 'wikibase-sense': entity, -} as const +type SimplifySnakOptions = SimplifyTimeSnakOptions & SimplifyEntitySnakOptions -export function parseClaim (datatype, datavalue, options, claimId) { +export function parseClaim ( + datatype: DataType | undefined, + datavalue: SnakValue, + options: SimplifySnakOptions, + claimId: string, +) { // Known case of missing datatype: form.claims, sense.claims - datatype = datatype || datavalue.type - // Known case requiring this: legacy "muscial notation" datatype - datatype = datatype.replace(' ', '-') - - try { - return parsers[datatype](datavalue, options) - } catch (err: unknown) { - if (err instanceof Error && err.message === 'parsers[datatype] is not a function') { - err.message = `${datatype} claim parser isn't implemented - Claim id: ${claimId} - Please report to https://github.com/maxlath/wikibase-sdk/issues` - } - throw err + // datavalue.type is used then + + // @ts-expect-error known case requiring this: legacy "musical notation" datatype + datatype = datatype?.replace(' ', '-') + + if ( + datatype === 'wikibase-form' || + datatype === 'wikibase-item' || + datatype === 'wikibase-lexeme' || + datatype === 'wikibase-property' || + datatype === 'wikibase-sense' || + datavalue.type === 'wikibase-entityid' + ) { + return entity(datavalue as SnakEntityValue, options) + } + + if (datatype === 'globe-coordinate' || datavalue.type === 'globecoordinate') { + return coordinate(datavalue as SnakGlobeCoordinateValue, options) } + + if (datatype === 'monolingualtext' || datavalue.type === 'monolingualtext') { + return monolingualtext(datavalue as SnakMonolingualTextValue, options) + } + + if (datatype === 'quantity' || datavalue.type === 'quantity') { + return quantity(datavalue as SnakQuantityValue, options) + } + + if (datatype === 'time' || datavalue.type === 'time') { + return time(datavalue as SnakTimeValue, options) + } + + if ( + datatype === 'commonsMedia' || + datatype === 'external-id' || + datatype === 'geo-shape' || + datatype === 'math' || + datatype === 'musical-notation' || + datatype === 'string' || + datatype === 'tabular-data' || + datatype === 'url' || + datavalue.type === 'string' + ) { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + return simple(datavalue as SnakStringValue) + } + + unknownClaimType(datatype, datavalue, claimId) +} + +// TypeScript notices when the argument isnt `never` and does not compile in that case -> some case is not implemented +function unknownClaimType ( + datatype: never, + datavalue: { readonly type: never }, + claimId: string, +): never { + const minimal = String(datatype) || String(datavalue) + const full = JSON.stringify({ datatype, datavalue }) + throw new Error(`${minimal} claim parser isn't implemented\nPlease report to https://github.com/maxlath/wikibase-sdk/issues\n\nClaim id: ${claimId}\n${full}`) } diff --git a/src/helpers/simplify_claims.ts b/src/helpers/simplify_claims.ts index 69c795f0..46c3abcd 100644 --- a/src/helpers/simplify_claims.ts +++ b/src/helpers/simplify_claims.ts @@ -1,14 +1,15 @@ import { uniq } from '../utils/utils.js' import { parseClaim } from './parse_claim.js' import { truthyPropertyClaims, nonDeprecatedPropertyClaims } from './rank.js' -import type { Claim, Claims, PropertyClaims, PropertyQualifiers, Qualifier, Qualifiers, Reference } from '../types/claim.js' +import type { Claim, Claims, DataType, PropertyClaims, PropertyQualifiers, QualifierSnak, Qualifiers, Reference, ReferenceSnak, SnakType } from '../types/claim.js' +import type { PropertyId } from '../types/entity.js' import type { SimplifiedClaim, SimplifiedClaims, SimplifiedPropertyClaims, SimplifySnakOptions, SimplifySnaksOptions } from '../types/simplify_claims.js' +import type { SnakValue } from '../types/snakvalue.js' -function simplifySnaks (snaks, options) { +function simplifySnaks (snaks: Record, options: SimplifySnaksOptions) { const { propertyPrefix } = options const simplifiedSnaks: any = {} - for (let id in snaks) { - const propertySnaks = snaks[id] + for (let [ id, propertySnaks ] of Object.entries(snaks)) { if (propertyPrefix) { id = propertyPrefix + ':' + id } @@ -17,7 +18,7 @@ function simplifySnaks (snaks, options) { return simplifiedSnaks } -function simplifyPropertySnaks (propertySnaks, options) { +function simplifyPropertySnaks (propertySnaks: any[], options: SimplifySnaksOptions) { // Avoid to throw on empty inputs to allow to simplify claims array // without having to know if the entity as claims for this property // Ex: simplifyPropertyClaims(entity.claims.P124211616) @@ -31,7 +32,7 @@ function simplifyPropertySnaks (propertySnaks, options) { propertySnaks = truthyPropertyClaims(propertySnaks) } - propertySnaks = propertySnaks + const simplified = propertySnaks .map(claim => simplifyClaim(claim, options)) // Filter-out novalue and somevalue claims, // unless a novalueValue or a somevalueValue is passed in options @@ -39,27 +40,32 @@ function simplifyPropertySnaks (propertySnaks, options) { .filter(obj => obj !== undefined) // Deduplicate values unless we return a rich value object - if (propertySnaks[0] && typeof propertySnaks[0] !== 'object') { - return uniq(propertySnaks) + if (simplified[0] && typeof simplified[0] !== 'object') { + return uniq(simplified) } else { - return propertySnaks + return simplified } } -// Expects a single snak object -// Ex: entity.claims.P369[0] -function simplifySnak (claim, options) { +/** + * tries to replace wikidata deep claim object by a simple value + * e.g. a string, an entity Qid or an epoch time number + * + * Expects a single snak object + * Ex: entity.claims.P369[0] + */ +function simplifySnak (claim: Claim | QualifierSnak | ReferenceSnak, options: SimplifySnakOptions) { const { keepQualifiers, keepReferences, keepIds, keepHashes, keepTypes, keepSnaktypes, keepRanks } = parseKeepOptions(options) - // tries to replace wikidata deep claim object by a simple value - // e.g. a string, an entity Qid or an epoch time number - const { mainsnak, rank } = claim - - let value, datatype, datavalue, snaktype, isQualifierSnak, isReferenceSnak - if (mainsnak) { - datatype = mainsnak.datatype - datavalue = mainsnak.datavalue - snaktype = mainsnak.snaktype + let datatype: DataType | undefined + let datavalue: SnakValue + let snaktype: SnakType + let isQualifierSnak: boolean + let isReferenceSnak: boolean + if ('mainsnak' in claim) { + datatype = claim.mainsnak.datatype + datavalue = claim.mainsnak.datavalue + snaktype = claim.mainsnak.snaktype } else { // Qualifiers have no mainsnak, and define datatype, datavalue on claim datavalue = claim.datavalue @@ -70,6 +76,7 @@ function simplifySnak (claim, options) { else isReferenceSnak = true } + let value: any if (datavalue) { value = parseClaim(datatype, datavalue, options, claim.id) } else { @@ -84,7 +91,7 @@ function simplifySnak (claim, options) { const valueObj: any = { value } - if (keepHashes) valueObj.hash = claim.hash + if (keepHashes && 'hash' in claim) valueObj.hash = claim.hash if (keepTypes) valueObj.type = datatype if (keepSnaktypes) valueObj.snaktype = snaktype @@ -109,18 +116,16 @@ function simplifySnak (claim, options) { if (keepSnaktypes) valueObj.snaktype = snaktype - if (keepRanks) valueObj.rank = rank + if (keepRanks && 'rank' in claim) valueObj.rank = claim.rank - const subSnaksOptions = getSubSnakOptions(options) - subSnaksOptions.keepHashes = keepHashes + const subSnaksOptions = { ...options, areSubSnaks: true } if (keepQualifiers) { - valueObj.qualifiers = simplifyQualifiers(claim.qualifiers, subSnaksOptions) + valueObj.qualifiers = 'qualifiers' in claim ? simplifyQualifiers(claim.qualifiers, subSnaksOptions) : {} } if (keepReferences) { - claim.references = claim.references || [] - valueObj.references = simplifyReferences(claim.references, subSnaksOptions) + valueObj.references = 'references' in claim ? simplifyReferences(claim.references, subSnaksOptions) : [] } if (keepIds) valueObj.id = claim.id @@ -139,38 +144,42 @@ export function simplifyClaim (claim: Claim, options: SimplifySnakOptions = {}): } export function simplifyQualifiers (qualifiers: Qualifiers, options: SimplifySnaksOptions = {}) { - return simplifySnaks(qualifiers, getSubSnakOptions(options)) + return simplifySnaks(qualifiers, { ...options, areSubSnaks: true }) } export function simplifyPropertyQualifiers (propertyQualifiers: PropertyQualifiers, options: SimplifySnaksOptions = {}) { - return simplifyPropertySnaks(propertyQualifiers, getSubSnakOptions(options)) + return simplifyPropertySnaks(propertyQualifiers, { ...options, areSubSnaks: true }) } -export function simplifyQualifier (qualifier: Qualifier, options: SimplifySnakOptions = {}) { +export function simplifyQualifier (qualifier: QualifierSnak, options: SimplifySnakOptions = {}) { return simplifySnak(qualifier, options) } -export function simplifyReferences (references: Reference[], options) { +export function simplifyReferences (references: Reference[], options: SimplifySnaksOptions) { return references.map(refRecord => simplifyReferenceRecord(refRecord, options)) } -export function simplifyReferenceRecord (refRecord, options) { - const subSnaksOptions = getSubSnakOptions(options) +export function simplifyReferenceRecord (refRecord: Reference, options: SimplifySnaksOptions) { + const subSnaksOptions = { ...options, areSubSnaks: true } const snaks = simplifySnaks(refRecord.snaks, subSnaksOptions) if (subSnaksOptions.keepHashes) return { snaks, hash: refRecord.hash } else return snaks } -const getSubSnakOptions = (options: any = {}) => { - if (options.areSubSnaks) return options - // Using a new object so that the original options object isn't modified - else return Object.assign({}, options, { areSubSnaks: true }) -} - -const keepOptions = [ 'keepQualifiers', 'keepReferences', 'keepIds', 'keepHashes', 'keepTypes', 'keepSnaktypes', 'keepRanks', 'keepRichValues' ] - -const parseKeepOptions = options => { +const keepOptions = [ + 'keepHashes', + 'keepIds', + 'keepQualifiers', + 'keepRanks', + 'keepReferences', + 'keepRichValues', + 'keepSnaktypes', + 'keepTypes', +] as const +type KeepOption = typeof keepOptions[number] + +const parseKeepOptions = (options: SimplifySnakOptions): Record => { if (options.keepAll) { keepOptions.forEach(optionName => { - if (options[optionName] == null) options[optionName] = true + options[optionName] = options[optionName] ?? true }) } - return options + return options as Record } diff --git a/src/types/claim.ts b/src/types/claim.ts index dc6a3930..005c3d95 100644 --- a/src/types/claim.ts +++ b/src/types/claim.ts @@ -1,9 +1,28 @@ import type { PropertyId } from './entity.js' -import type { parsers } from '../helpers/parse_claim.js' +import type { SnakValue } from './snakvalue.js' export type Rank = 'normal' | 'preferred' | 'deprecated' export type SnakType = 'value' | 'somevalue' | 'novalue' -export type DataType = keyof typeof parsers +export const DataTypes = [ + 'commonsMedia', + 'external-id', + 'geo-shape', + 'globe-coordinate', + 'math', + 'monolingualtext', + 'musical-notation', + 'quantity', + 'string', + 'tabular-data', + 'time', + 'url', + 'wikibase-form', + 'wikibase-item', + 'wikibase-lexeme', + 'wikibase-property', + 'wikibase-sense', +] as const +export type DataType = typeof DataTypes[number] export interface Claim { id: string @@ -24,61 +43,18 @@ export type Snaks = Record export interface Snak { // A mainsnak object won't have an id, as its already on the claim id?: string - datatype: string + datatype: DataType datavalue?: SnakValue hash: string property: string snaktype: SnakType } -export interface SnakValue { - type: DataType - value: unknown -} - -export interface ClaimSnakTimeValue extends SnakValue { - type: 'time' - value: { - after: number - before: number - calendermodel: string - precision: number - time: string - timezone: number - } -} - -export interface ClaimSnakQuantity extends SnakValue { - type: 'quantity' - value: { - amount: string - unit: string - upperBound?: string - lowerBound?: string - } -} - -export interface ClaimSnakString extends SnakValue { - type: 'string' - value: string -} - -export interface SnakEntityValue extends SnakValue { - type: 'wikibase-entityid' - value: { - id: string - 'numeric-id': number - 'entity-type': string - } -} - -export type ClaimSnakWikibaseItem = SnakEntityValue - -export interface Qualifier extends Snak { +export interface QualifierSnak extends Snak { id: string } -export type PropertyQualifiers = Qualifier[] +export type PropertyQualifiers = QualifierSnak[] export type Qualifiers = Record @@ -89,7 +65,7 @@ export interface ReferenceSnak extends Snak { export interface Reference { hash: string snaks: Record - 'snaks-order': string[] + 'snaks-order': PropertyId[] } export type References = Reference[] diff --git a/src/types/simplify_claims.ts b/src/types/simplify_claims.ts index fc9c3968..4d1ab698 100644 --- a/src/types/simplify_claims.ts +++ b/src/types/simplify_claims.ts @@ -5,21 +5,22 @@ import type { TimeConverter, TimeConverterFn } from '../helpers/wikibase_time.js export interface SimplifySnakOptions { entityPrefix?: string propertyPrefix?: string - keepRichValues?: boolean - keepTypes?: boolean - keepQualifiers?: boolean - keepReferences?: boolean - keepIds?: boolean + keepAll?: boolean keepHashes?: boolean + keepIds?: boolean + keepQualifiers?: boolean keepRanks?: boolean + keepReferences?: boolean + keepRichValues?: boolean keepSnaktypes?: boolean - keepAll?: boolean + keepTypes?: boolean timeConverter?: TimeConverter | TimeConverterFn novalueValue?: any somevalueValue?: any } export interface SimplifySnaksOptions extends SimplifySnakOptions { + areSubSnaks?: boolean keepNonTruthy?: boolean keepNonDeprecated?: boolean } diff --git a/src/types/snakvalue.ts b/src/types/snakvalue.ts new file mode 100644 index 00000000..ae8f5cc3 --- /dev/null +++ b/src/types/snakvalue.ts @@ -0,0 +1,68 @@ +import type { EntityType } from './entity.js' +import type { WmLanguageCode } from './options.js' + +export type SnakValue = + | SnakEntityValue + | SnakGlobeCoordinateValue + | SnakMonolingualTextValue + | SnakQuantityValue + | SnakStringValue + | SnakTimeValue + +export interface SnakEntityValue { + readonly type: 'wikibase-entityid' + value: { + 'numeric-id': number + 'entity-type': EntityType + } | { + id: string + 'numeric-id'?: number + 'entity-type': EntityType + } +} + +export interface SnakGlobeCoordinateValue { + readonly type: 'globecoordinate' + value: { + latitude: number + longitude: number + altitude?: number + precision: number + globe: string + } +} + +export interface SnakMonolingualTextValue { + readonly type: 'monolingualtext' + value: { + language: WmLanguageCode + text: string + } +} + +export interface SnakQuantityValue { + readonly type: 'quantity' + value: { + amount: string + unit: string + upperBound?: string + lowerBound?: string + } +} + +export interface SnakStringValue { + readonly type: 'string' + value: string +} + +export interface SnakTimeValue { + readonly type: 'time' + value: { + after: number + before: number + calendarmodel: string + precision: number + time: string + timezone: number + } +}