From c30f9b6b6942cc9cd27d21c14139ebd714768fb0 Mon Sep 17 00:00:00 2001 From: David LJ Date: Wed, 2 Oct 2024 16:58:11 +0200 Subject: [PATCH 01/13] feat: introduce meta element APIs v2 --- .../ngx-meta/api-extractor/ngx-meta.api.md | 36 ++++++ .../src/core/src/meta-elements/index.ts | 1 + .../v2/__tests__/add-meta-elements.ts | 5 + .../v2/__tests__/clear-metas-after-each.ts | 10 ++ .../get-meta-elements-by-selector.ts | 5 + .../v2/__tests__/html-attributes-to-json.ts | 5 + .../src/core/src/meta-elements/v2/index.ts | 14 +++ .../meta-elements/v2/meta-elements-helpers.ts | 36 ++++++ .../v2/ngx-meta-element-attributes.ts | 29 +++++ .../v2/ngx-meta-element-setter.spec.ts | 117 ++++++++++++++++++ .../v2/ngx-meta-element-setter.ts | 35 ++++++ .../v2/ngx-meta-elements-setter.spec.ts | 108 ++++++++++++++++ .../v2/ngx-meta-elements-setter.ts | 35 ++++++ 13 files changed, 436 insertions(+) create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/add-meta-elements.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/clear-metas-after-each.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/get-meta-elements-by-selector.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/html-attributes-to-json.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/index.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/meta-elements-helpers.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts diff --git a/projects/ngx-meta/api-extractor/ngx-meta.api.md b/projects/ngx-meta/api-extractor/ngx-meta.api.md index 63410828..19b88141 100644 --- a/projects/ngx-meta/api-extractor/ngx-meta.api.md +++ b/projects/ngx-meta/api-extractor/ngx-meta.api.md @@ -186,6 +186,12 @@ export type MetadataSetterFactory = (...deps: Exclude; + +// @alpha (undocumented) +export const NGX_META_ELEMENTS_SETTER: InjectionToken; + // @public export class NgxMetaCoreModule { // Warning: (ae-forgotten-export) The symbol "CoreFeatures" needs to be exported by the entry point all-entry-points.d.ts @@ -199,6 +205,27 @@ export interface NgxMetaCoreModuleForRootOptions { defaults?: MetadataValues; } +// @alpha +export type NgxMetaElementAttributes = Partial<{ + charset: string; + content: string; + 'http-equiv': string; + id: string; + itemprop: string; + name: string; + property: string; + scheme: string; + url: string; +}> & { + [key: string]: string; +}; + +// @alpha (undocumented) +export type NgxMetaElementSetter = (nameAttribute: readonly [name: string, value: string], content: NgxMetaElementAttributes | undefined) => void; + +// @alpha (undocumented) +export type NgxMetaElementsSetter = (nameAttribute: readonly [name: string, value: string], contents: ReadonlyArray) => void; + // @public export class NgxMetaJsonLdModule { } @@ -554,12 +581,21 @@ export type _UrlResolver = (url: URL | string | undefined | null | AngularRouter // @internal export const _urlResolver: _LazyInjectionToken<_UrlResolver>; +// @alpha +export const withContentAttribute: (content: string | null | undefined) => NgxMetaElementAttributes | undefined; + +// @alpha +export const withNameAttribute: (value: string) => readonly ["name", string]; + // @public export const withNgxMetaBaseUrl: (baseUrl: BaseUrl) => CoreFeature; // @public export const withNgxMetaDefaults: (defaults: MetadataValues) => CoreFeature; +// @alpha +export const withPropertyAttribute: (value: string) => string[]; + // (No @packageDocumentation comment for this package) ``` diff --git a/projects/ngx-meta/src/core/src/meta-elements/index.ts b/projects/ngx-meta/src/core/src/meta-elements/index.ts index b520ad3e..67984659 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/index.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/index.ts @@ -9,3 +9,4 @@ export { export { NgxMetaMetaService } from './ngx-meta-meta.service' export { NgxMetaMetaContent } from './ngx-meta-meta-content' export { NgxMetaMetaDefinition } from './ngx-meta-meta-definition' +export * from './v2' diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/add-meta-elements.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/add-meta-elements.ts new file mode 100644 index 00000000..f45f8811 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/add-meta-elements.ts @@ -0,0 +1,5 @@ +import { TestBed } from '@angular/core/testing' +import { Meta, MetaDefinition } from '@angular/platform-browser' + +export const addMetaElements = (tags: ReadonlyArray) => + TestBed.inject(Meta).addTags(tags as MetaDefinition[]) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/clear-metas-after-each.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/clear-metas-after-each.ts new file mode 100644 index 00000000..e103ce5a --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/clear-metas-after-each.ts @@ -0,0 +1,10 @@ +import { TestBed } from '@angular/core/testing' +import { Meta } from '@angular/platform-browser' + +export const clearMetasAfterEach = (attributeSelector: string) => { + afterEach(() => { + TestBed.inject(Meta) + .getTags(attributeSelector) + .forEach((tag) => tag.remove()) + }) +} diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/get-meta-elements-by-selector.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/get-meta-elements-by-selector.ts new file mode 100644 index 00000000..5452a321 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/get-meta-elements-by-selector.ts @@ -0,0 +1,5 @@ +import { TestBed } from '@angular/core/testing' +import { Meta } from '@angular/platform-browser' + +export const getMetaElementsBySelector = (attrSelector: string) => + TestBed.inject(Meta).getTags(attrSelector) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/html-attributes-to-json.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/html-attributes-to-json.ts new file mode 100644 index 00000000..35d88dc4 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/html-attributes-to-json.ts @@ -0,0 +1,5 @@ +export const htmlAttributesToJson = (attributes: NamedNodeMap): object => + [...Array(attributes.length).keys()] + .map((index) => attributes.item(index)) + .map((item) => (item ? { [item.name]: item.value } : {})) + .reduce((acc, curr) => ({ ...acc, ...curr }), {}) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts new file mode 100644 index 00000000..5612705e --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts @@ -0,0 +1,14 @@ +export { + NGX_META_ELEMENT_SETTER, + NgxMetaElementSetter, +} from './ngx-meta-element-setter' +export { + NGX_META_ELEMENTS_SETTER, + NgxMetaElementsSetter, +} from './ngx-meta-elements-setter' +export { NgxMetaElementAttributes } from './ngx-meta-element-attributes' +export { + withNameAttribute, + withPropertyAttribute, + withContentAttribute, +} from './meta-elements-helpers' diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/meta-elements-helpers.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/meta-elements-helpers.ts new file mode 100644 index 00000000..ca458dff --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/meta-elements-helpers.ts @@ -0,0 +1,36 @@ +import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' + +/** + * Utility function to specify a `` element. + * + * See {@link NgxMetaElementSetter} for examples. + * + * @param value - Value for the `name` attribute of the `` element + * + * @alpha + */ +export const withNameAttribute = (value: string) => ['name', value] as const +/** + * Utility function to specify a `` element. + * + * See {@link NgxMetaElementSetter} for examples. + * + * @param value - Value for the `property` attribute of the `` element + * + * @alpha + */ +export const withPropertyAttribute = (value: string) => ['property', value] + +/** + * Utility function to create an {@link NgxMetaElementAttributes} specifying the `content` attribute to the + * given `value`. Unless given `value` is `null` or `undefined`. In that case, `undefined` is returned. + * + * See {@link NgxMetaElementSetter} for examples. + * + * @param content - Value for the `property` attribute of the `` element + * + * @alpha + */ +export const withContentAttribute = ( + content: string | null | undefined, +): NgxMetaElementAttributes | undefined => (content ? { content } : undefined) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts new file mode 100644 index 00000000..5df0d22f --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts @@ -0,0 +1,29 @@ +// noinspection JSValidateJSDoc +/** + * Models a `` element HTML's attributes as a key / value map. + * + * Inspired on Angular's {@link https://angular.dev/api/platform-browser/MetaDefinition/ | MetaDefinition} + * + * Only difference is `http-equiv` property. + * + * @alpha + */ +export type NgxMetaElementAttributes = Partial<{ + charset: string + content: string + /** + * In an Angular's {@link https://angular.dev/api/platform-browser/MetaDefinition/ | MetaDefinition}, + * `httpEquiv` would also be accepted. This way no need to quote the key property. + * + * This way there's no need to map attribute names. + */ + 'http-equiv': string + id: string + itemprop: string + name: string + property: string + scheme: string + url: string +}> & { + [key: string]: string +} diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts new file mode 100644 index 00000000..66831459 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts @@ -0,0 +1,117 @@ +import { TestBed } from '@angular/core/testing' +import { + NGX_META_ELEMENT_SETTER, + NgxMetaElementSetter, +} from './ngx-meta-element-setter' +import { + withContentAttribute, + withNameAttribute, +} from './meta-elements-helpers' +import { htmlAttributesToJson } from './__tests__/html-attributes-to-json' +import { getMetaElementsBySelector } from './__tests__/get-meta-elements-by-selector' +import { clearMetasAfterEach } from './__tests__/clear-metas-after-each' +import { addMetaElements } from './__tests__/add-meta-elements' + +describe('Meta element setter', () => { + const dummyMetaNameAttribute = withNameAttribute('dummy') + const dummyMetaAttributeSelector = 'name="dummy"' + const dummyMetaContentAttribute = withContentAttribute('dummy') + const dummyMetaAttributes = { name: 'dummy', content: 'dummy' } + + clearMetasAfterEach(dummyMetaAttributeSelector) + + describe('when no element exists yet', () => { + describe('when no content is provided', () => { + it('should not create the meta element', () => { + const sut = makeSut() + + sut(dummyMetaNameAttribute, undefined) + + const elements = getMetaElementsBySelector(dummyMetaAttributeSelector) + expect(elements).toHaveSize(0) + }) + }) + + describe('when content is provided', () => { + it('should create the meta element', () => { + const sut = makeSut() + + sut(dummyMetaNameAttribute, dummyMetaContentAttribute) + + const elements = getMetaElementsBySelector(dummyMetaAttributeSelector) + expect(elements).toHaveSize(1) + const element = elements[0] + expect(htmlAttributesToJson(element.attributes)).toEqual( + dummyMetaAttributes, + ) + }) + }) + }) + + describe('when an element already exists', () => { + let sut: NgxMetaElementSetter + + beforeEach(() => { + sut = makeSut() + addMetaElements([dummyMetaAttributes]) + expect(getMetaElementsBySelector(dummyMetaAttributeSelector)) + .withContext('test setup: element should exist') + .toHaveSize(1) + }) + + describe('when no content is provided', () => { + it('should remove the element', () => { + sut(dummyMetaNameAttribute, undefined) + + expect( + getMetaElementsBySelector(dummyMetaAttributeSelector), + ).toHaveSize(0) + }) + }) + + describe('when content is provided', () => { + const anotherContent = 'another-dummy-content' + + it('should update the element', () => { + sut(dummyMetaNameAttribute, withContentAttribute(anotherContent)) + + const elements = getMetaElementsBySelector(dummyMetaAttributeSelector) + expect(elements).toHaveSize(1) + const element = elements[0] + expect(htmlAttributesToJson(element.attributes)).toEqual({ + ...dummyMetaAttributes, + content: anotherContent, + }) + }) + }) + }) + + describe('with content attribute utility function', () => { + const sut = withContentAttribute + + describe('when no content is provided', () => { + const TEST_CASES = [null, undefined] + + TEST_CASES.forEach((testCase) => { + describe(`like when ${testCase}`, () => { + it('should return nothing', () => { + expect(sut(testCase)).toBeUndefined() + }) + }) + }) + }) + + describe('when content is provided', () => { + const dummyContent = 'dummy' + + it('should return the content value inside the content key', () => { + expect(sut(dummyContent)).toEqual({ content: dummyContent }) + }) + }) + }) +}) + +const makeSut = (): NgxMetaElementSetter => { + TestBed.configureTestingModule({}) + return TestBed.inject(NGX_META_ELEMENT_SETTER) +} diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts new file mode 100644 index 00000000..e3db9e25 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts @@ -0,0 +1,35 @@ +import { inject, InjectionToken } from '@angular/core' +import { Meta } from '@angular/platform-browser' +import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' + +/** + * @alpha + */ +export const NGX_META_ELEMENT_SETTER = new InjectionToken( + ngDevMode ? 'NgxMeta Meta elements setter' : 'NgxMetaMES', + { + factory: () => { + const meta = inject(Meta) + return (nameAttribute, content) => { + const [nameAttributeName, nameAttributeValue] = nameAttribute + const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` + if (!content) { + meta.removeTag(attrSelector) + return + } + meta.updateTag( + { [nameAttributeName]: nameAttributeValue, ...content }, + attrSelector, + ) + } + }, + }, +) + +/** + * @alpha + */ +export type NgxMetaElementSetter = ( + nameAttribute: readonly [name: string, value: string], + content: NgxMetaElementAttributes | undefined, +) => void diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts new file mode 100644 index 00000000..a719d4f3 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts @@ -0,0 +1,108 @@ +import { + withContentAttribute, + withNameAttribute, +} from './meta-elements-helpers' +import { getMetaElementsBySelector } from './__tests__/get-meta-elements-by-selector' +import { TestBed } from '@angular/core/testing' +import { + NGX_META_ELEMENTS_SETTER, + NgxMetaElementsSetter, +} from './ngx-meta-elements-setter' +import { htmlAttributesToJson } from './__tests__/html-attributes-to-json' +import { clearMetasAfterEach } from './__tests__/clear-metas-after-each' +import { addMetaElements } from './__tests__/add-meta-elements' + +describe('Meta elements setter', () => { + const dummyMetaNameAttribute = withNameAttribute('dummy') + const dummyMetaAttributeSelector = 'name="dummy"' + const dummyMetaContentAttribute = withContentAttribute('dummy') + const dummyMetaAttributes = { name: 'dummy', content: 'dummy' } + const anotherDummyMetaContentAttribute = withContentAttribute('another-dummy') + const anotherDummyMetaAttributes = { name: 'dummy', content: 'another-dummy' } + + clearMetasAfterEach(dummyMetaAttributeSelector) + + describe('when no elements exist yet', () => { + describe('when no contents are provided', () => { + it('should not create any element', () => { + const sut = makeSut() + + sut(dummyMetaNameAttribute, []) + + expect( + getMetaElementsBySelector(dummyMetaAttributeSelector), + ).toHaveSize(0) + }) + }) + + describe('when contents are provided', () => { + it('should create an element for each one', () => { + const sut = makeSut() + sut(dummyMetaNameAttribute, [ + dummyMetaContentAttribute, + anotherDummyMetaContentAttribute, + ]) + + const elements = getMetaElementsBySelector(dummyMetaAttributeSelector) + expect( + elements.map((e) => e.attributes).map(htmlAttributesToJson), + ).toEqual([dummyMetaAttributes, anotherDummyMetaAttributes]) + }) + }) + }) + + describe('when elements exist already', () => { + let sut: NgxMetaElementsSetter + + beforeEach(() => { + sut = makeSut() + + const dummyNameAttribute = { + [dummyMetaNameAttribute[0]]: dummyMetaNameAttribute[1], + } + addMetaElements([ + { + ...dummyNameAttribute, + content: 'existing-content-1', + }, + { + ...dummyNameAttribute, + content: 'existing-content-2', + }, + ]) + expect(getMetaElementsBySelector(dummyMetaAttributeSelector)) + .withContext('test setup: two elements should exist') + .toHaveSize(2) + }) + + describe('when no contents are provided', () => { + it('should remove them all', () => { + sut(dummyMetaNameAttribute, []) + + expect( + getMetaElementsBySelector(dummyMetaAttributeSelector), + ).toHaveSize(0) + }) + }) + + describe('when contents are provided', () => { + it('should replace existing elements', () => { + sut(dummyMetaNameAttribute, [ + dummyMetaContentAttribute, + anotherDummyMetaContentAttribute, + ]) + + expect( + getMetaElementsBySelector(dummyMetaAttributeSelector) + .map((e) => e.attributes) + .map(htmlAttributesToJson), + ).toEqual([dummyMetaAttributes, anotherDummyMetaAttributes]) + }) + }) + }) +}) + +const makeSut = () => { + TestBed.configureTestingModule({}) + return TestBed.inject(NGX_META_ELEMENTS_SETTER) +} diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts new file mode 100644 index 00000000..2ae56422 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts @@ -0,0 +1,35 @@ +import { inject, InjectionToken } from '@angular/core' +import { Meta } from '@angular/platform-browser' +import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' + +/** + * @alpha + */ +export const NGX_META_ELEMENTS_SETTER = + new InjectionToken( + ngDevMode ? 'NgxMeta Meta elements setter' : 'NgxMetaMEsS', + { + factory: () => { + const meta = inject(Meta) + return (nameAttribute, contents) => { + const [nameAttributeName, nameAttributeValue] = nameAttribute + const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` + meta.getTags(attrSelector).forEach((tag) => tag.remove()) + meta.addTags( + contents.map((content) => ({ + [nameAttributeName]: nameAttributeValue, + ...content, + })), + ) + } + }, + }, + ) + +/** + * @alpha + */ +export type NgxMetaElementsSetter = ( + nameAttribute: readonly [name: string, value: string], + contents: ReadonlyArray, +) => void From d91b02f090e52a5f47e0c92912c3dd72587a8f67 Mon Sep 17 00:00:00 2001 From: David LJ Date: Mon, 7 Oct 2024 12:50:26 +0200 Subject: [PATCH 02/13] chore: ignore branch from coverage count --- .../src/core/src/meta-elements/v2/ngx-meta-element-setter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts index e3db9e25..18671611 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts @@ -13,6 +13,7 @@ export const NGX_META_ELEMENT_SETTER = new InjectionToken( return (nameAttribute, content) => { const [nameAttributeName, nameAttributeValue] = nameAttribute const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` + /* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */ if (!content) { meta.removeTag(attrSelector) return From ecf6cf3373943f0ab0c2085c725443217be2a2f5 Mon Sep 17 00:00:00 2001 From: David LJ Date: Mon, 7 Oct 2024 12:52:34 +0200 Subject: [PATCH 03/13] chore: try removing an exports layer --- .../ngx-meta/src/core/src/meta-elements/index.ts | 16 +++++++++++++++- .../src/core/src/meta-elements/v2/index.ts | 14 -------------- 2 files changed, 15 insertions(+), 15 deletions(-) delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/index.ts diff --git a/projects/ngx-meta/src/core/src/meta-elements/index.ts b/projects/ngx-meta/src/core/src/meta-elements/index.ts index 67984659..fadff206 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/index.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/index.ts @@ -9,4 +9,18 @@ export { export { NgxMetaMetaService } from './ngx-meta-meta.service' export { NgxMetaMetaContent } from './ngx-meta-meta-content' export { NgxMetaMetaDefinition } from './ngx-meta-meta-definition' -export * from './v2' +// v2 +export { + NGX_META_ELEMENT_SETTER, + NgxMetaElementSetter, +} from './v2/ngx-meta-element-setter' +export { + NGX_META_ELEMENTS_SETTER, + NgxMetaElementsSetter, +} from './v2/ngx-meta-elements-setter' +export { NgxMetaElementAttributes } from './v2/ngx-meta-element-attributes' +export { + withNameAttribute, + withPropertyAttribute, + withContentAttribute, +} from './v2/meta-elements-helpers' diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts deleted file mode 100644 index 5612705e..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export { - NGX_META_ELEMENT_SETTER, - NgxMetaElementSetter, -} from './ngx-meta-element-setter' -export { - NGX_META_ELEMENTS_SETTER, - NgxMetaElementsSetter, -} from './ngx-meta-elements-setter' -export { NgxMetaElementAttributes } from './ngx-meta-element-attributes' -export { - withNameAttribute, - withPropertyAttribute, - withContentAttribute, -} from './meta-elements-helpers' From df28b697bea36f94d4439044d980639b56ee1847 Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 00:22:58 +0200 Subject: [PATCH 04/13] refactor: use lazy injection tokens --- .../ngx-meta/api-extractor/ngx-meta.api.md | 16 +++++--- .../src/core/src/meta-elements/index.ts | 16 +------- .../src/core/src/meta-elements/v2/index.ts | 15 +++++++ .../v2/ngx-meta-element-attributes.ts | 5 +-- .../v2/ngx-meta-element-setter.spec.ts | 4 +- .../v2/ngx-meta-element-setter.ts | 41 +++++++++---------- .../v2/ngx-meta-elements-setter.spec.ts | 4 +- .../v2/ngx-meta-elements-setter.ts | 40 +++++++++--------- 8 files changed, 71 insertions(+), 70 deletions(-) create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/index.ts diff --git a/projects/ngx-meta/api-extractor/ngx-meta.api.md b/projects/ngx-meta/api-extractor/ngx-meta.api.md index 19b88141..d97183b3 100644 --- a/projects/ngx-meta/api-extractor/ngx-meta.api.md +++ b/projects/ngx-meta/api-extractor/ngx-meta.api.md @@ -186,12 +186,6 @@ export type MetadataSetterFactory = (...deps: Exclude; - -// @alpha (undocumented) -export const NGX_META_ELEMENTS_SETTER: InjectionToken; - // @public export class NgxMetaCoreModule { // Warning: (ae-forgotten-export) The symbol "CoreFeatures" needs to be exported by the entry point all-entry-points.d.ts @@ -223,9 +217,19 @@ export type NgxMetaElementAttributes = Partial<{ // @alpha (undocumented) export type NgxMetaElementSetter = (nameAttribute: readonly [name: string, value: string], content: NgxMetaElementAttributes | undefined) => void; +// Warning: (ae-incompatible-release-tags) The symbol "ngxMetaElementSetter" is marked as @alpha, but its signature references "_LazyInjectionToken" which is marked as @internal +// +// @alpha (undocumented) +export const ngxMetaElementSetter: _LazyInjectionToken; + // @alpha (undocumented) export type NgxMetaElementsSetter = (nameAttribute: readonly [name: string, value: string], contents: ReadonlyArray) => void; +// Warning: (ae-incompatible-release-tags) The symbol "ngxMetaElementsSetter" is marked as @alpha, but its signature references "_LazyInjectionToken" which is marked as @internal +// +// @alpha (undocumented) +export const ngxMetaElementsSetter: _LazyInjectionToken; + // @public export class NgxMetaJsonLdModule { } diff --git a/projects/ngx-meta/src/core/src/meta-elements/index.ts b/projects/ngx-meta/src/core/src/meta-elements/index.ts index fadff206..67984659 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/index.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/index.ts @@ -9,18 +9,4 @@ export { export { NgxMetaMetaService } from './ngx-meta-meta.service' export { NgxMetaMetaContent } from './ngx-meta-meta-content' export { NgxMetaMetaDefinition } from './ngx-meta-meta-definition' -// v2 -export { - NGX_META_ELEMENT_SETTER, - NgxMetaElementSetter, -} from './v2/ngx-meta-element-setter' -export { - NGX_META_ELEMENTS_SETTER, - NgxMetaElementsSetter, -} from './v2/ngx-meta-elements-setter' -export { NgxMetaElementAttributes } from './v2/ngx-meta-element-attributes' -export { - withNameAttribute, - withPropertyAttribute, - withContentAttribute, -} from './v2/meta-elements-helpers' +export * from './v2' diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts new file mode 100644 index 00000000..2646d760 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts @@ -0,0 +1,15 @@ +// v2 +export { + ngxMetaElementSetter, + NgxMetaElementSetter, +} from './ngx-meta-element-setter' +export { + ngxMetaElementsSetter, + NgxMetaElementsSetter, +} from './ngx-meta-elements-setter' +export { NgxMetaElementAttributes } from './ngx-meta-element-attributes' +export { + withNameAttribute, + withPropertyAttribute, + withContentAttribute, +} from './meta-elements-helpers' diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts index 5df0d22f..1220a17c 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts @@ -1,4 +1,3 @@ -// noinspection JSValidateJSDoc /** * Models a `` element HTML's attributes as a key / value map. * @@ -13,9 +12,9 @@ export type NgxMetaElementAttributes = Partial<{ content: string /** * In an Angular's {@link https://angular.dev/api/platform-browser/MetaDefinition/ | MetaDefinition}, - * `httpEquiv` would also be accepted. This way no need to quote the key property. + * `httpEquiv` would also be accepted. This way there's no need to quote the key property. * - * This way there's no need to map attribute names. + * But without `httpEquiv` there's no need to map attribute names. So one more bit of code less around. */ 'http-equiv': string id: string diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts index 66831459..231e5d99 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from '@angular/core/testing' import { - NGX_META_ELEMENT_SETTER, + ngxMetaElementSetter, NgxMetaElementSetter, } from './ngx-meta-element-setter' import { @@ -113,5 +113,5 @@ describe('Meta element setter', () => { const makeSut = (): NgxMetaElementSetter => { TestBed.configureTestingModule({}) - return TestBed.inject(NGX_META_ELEMENT_SETTER) + return TestBed.inject(ngxMetaElementSetter()) } diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts index 18671611..9352e9d3 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts @@ -1,31 +1,30 @@ -import { inject, InjectionToken } from '@angular/core' +import { inject } from '@angular/core' import { Meta } from '@angular/platform-browser' import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' +import { _LazyInjectionToken, _makeInjectionToken } from '../../utils' /** * @alpha */ -export const NGX_META_ELEMENT_SETTER = new InjectionToken( - ngDevMode ? 'NgxMeta Meta elements setter' : 'NgxMetaMES', - { - factory: () => { - const meta = inject(Meta) - return (nameAttribute, content) => { - const [nameAttributeName, nameAttributeValue] = nameAttribute - const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` - /* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */ - if (!content) { - meta.removeTag(attrSelector) - return - } - meta.updateTag( - { [nameAttributeName]: nameAttributeValue, ...content }, - attrSelector, - ) +export const ngxMetaElementSetter: _LazyInjectionToken< + NgxMetaElementSetter +> = () => + _makeInjectionToken(ngDevMode ? 'Meta element setter' : 'MES', () => { + const meta = inject(Meta) + return (nameAttribute, content) => { + const [nameAttributeName, nameAttributeValue] = nameAttribute + const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` + /* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */ + if (!content) { + meta.removeTag(attrSelector) + return } - }, - }, -) + meta.updateTag( + { [nameAttributeName]: nameAttributeValue, ...content }, + attrSelector, + ) + } + }) /** * @alpha diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts index a719d4f3..6f7e0a39 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts @@ -5,7 +5,7 @@ import { import { getMetaElementsBySelector } from './__tests__/get-meta-elements-by-selector' import { TestBed } from '@angular/core/testing' import { - NGX_META_ELEMENTS_SETTER, + ngxMetaElementsSetter, NgxMetaElementsSetter, } from './ngx-meta-elements-setter' import { htmlAttributesToJson } from './__tests__/html-attributes-to-json' @@ -104,5 +104,5 @@ describe('Meta elements setter', () => { const makeSut = () => { TestBed.configureTestingModule({}) - return TestBed.inject(NGX_META_ELEMENTS_SETTER) + return TestBed.inject(ngxMetaElementsSetter()) } diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts index 2ae56422..30d7ec37 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts @@ -1,30 +1,28 @@ -import { inject, InjectionToken } from '@angular/core' +import { inject } from '@angular/core' import { Meta } from '@angular/platform-browser' import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' +import { _LazyInjectionToken, _makeInjectionToken } from '../../utils' /** * @alpha */ -export const NGX_META_ELEMENTS_SETTER = - new InjectionToken( - ngDevMode ? 'NgxMeta Meta elements setter' : 'NgxMetaMEsS', - { - factory: () => { - const meta = inject(Meta) - return (nameAttribute, contents) => { - const [nameAttributeName, nameAttributeValue] = nameAttribute - const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` - meta.getTags(attrSelector).forEach((tag) => tag.remove()) - meta.addTags( - contents.map((content) => ({ - [nameAttributeName]: nameAttributeValue, - ...content, - })), - ) - } - }, - }, - ) +export const ngxMetaElementsSetter: _LazyInjectionToken< + NgxMetaElementsSetter +> = () => + _makeInjectionToken(ngDevMode ? 'Meta elements setter' : 'MEsS', () => { + const meta = inject(Meta) + return (nameAttribute, contents) => { + const [nameAttributeName, nameAttributeValue] = nameAttribute + const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` + meta.getTags(attrSelector).forEach((tag) => tag.remove()) + meta.addTags( + contents.map((content) => ({ + [nameAttributeName]: nameAttributeValue, + ...content, + })), + ) + } + }) /** * @alpha From cb8c2428bf15640a071e0aa8bee0263b4b3de18a Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 14:11:44 +0200 Subject: [PATCH 05/13] refactor: turn API into a tree-shakeable service split helpers into files remove injection token based functions merge one and many into one API --- .../ngx-meta/api-extractor/ngx-meta.api.md | 27 ++- .../v2/__tests__/add-meta-elements.ts | 5 - .../v2/__tests__/clear-metas-after-each.ts | 10 - .../get-meta-elements-by-selector.ts | 5 - .../v2/__tests__/html-attributes-to-json.ts | 5 - .../src/core/src/meta-elements/v2/index.ts | 19 +- .../meta-elements/v2/meta-elements-helpers.ts | 36 ---- .../v2/ngx-meta-element-setter.spec.ts | 117 ------------ .../v2/ngx-meta-element-setter.ts | 35 ---- .../v2/ngx-meta-elements-setter.spec.ts | 108 ----------- .../v2/ngx-meta-elements-setter.ts | 33 ---- .../v2/ngx-meta-elements.service.spec.ts | 172 ++++++++++++++++++ .../v2/ngx-meta-elements.service.ts | 48 +++++ .../v2/with-content-attribute.ts | 19 ++ .../meta-elements/v2/with-name-attribute.ts | 13 ++ .../v2/with-property-attribute.ts | 13 ++ 16 files changed, 283 insertions(+), 382 deletions(-) delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/add-meta-elements.ts delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/clear-metas-after-each.ts delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/get-meta-elements-by-selector.ts delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/html-attributes-to-json.ts delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/meta-elements-helpers.ts delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts delete mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.spec.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/with-name-attribute.ts create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts diff --git a/projects/ngx-meta/api-extractor/ngx-meta.api.md b/projects/ngx-meta/api-extractor/ngx-meta.api.md index d97183b3..4b21a186 100644 --- a/projects/ngx-meta/api-extractor/ngx-meta.api.md +++ b/projects/ngx-meta/api-extractor/ngx-meta.api.md @@ -215,20 +215,14 @@ export type NgxMetaElementAttributes = Partial<{ }; // @alpha (undocumented) -export type NgxMetaElementSetter = (nameAttribute: readonly [name: string, value: string], content: NgxMetaElementAttributes | undefined) => void; +export type NgxMetaElementNameAttribute = readonly [name: string, value: string]; -// Warning: (ae-incompatible-release-tags) The symbol "ngxMetaElementSetter" is marked as @alpha, but its signature references "_LazyInjectionToken" which is marked as @internal -// -// @alpha (undocumented) -export const ngxMetaElementSetter: _LazyInjectionToken; - -// @alpha (undocumented) -export type NgxMetaElementsSetter = (nameAttribute: readonly [name: string, value: string], contents: ReadonlyArray) => void; - -// Warning: (ae-incompatible-release-tags) The symbol "ngxMetaElementsSetter" is marked as @alpha, but its signature references "_LazyInjectionToken" which is marked as @internal -// -// @alpha (undocumented) -export const ngxMetaElementsSetter: _LazyInjectionToken; +// @alpha +export class NgxMetaElementsService { + constructor(meta: Meta); + // (undocumented) + set(nameAttribute: NgxMetaElementNameAttribute, content: ReadonlyArray | NgxMetaElementAttributes | undefined): void; +} // @public export class NgxMetaJsonLdModule { @@ -586,7 +580,10 @@ export type _UrlResolver = (url: URL | string | undefined | null | AngularRouter export const _urlResolver: _LazyInjectionToken<_UrlResolver>; // @alpha -export const withContentAttribute: (content: string | null | undefined) => NgxMetaElementAttributes | undefined; +export const withContentAttribute: { + (content: null | undefined): undefined; + (content: string): NgxMetaElementAttributes; +}; // @alpha export const withNameAttribute: (value: string) => readonly ["name", string]; @@ -598,7 +595,7 @@ export const withNgxMetaBaseUrl: (baseUrl: BaseUrl) => CoreFeature CoreFeature; // @alpha -export const withPropertyAttribute: (value: string) => string[]; +export const withPropertyAttribute: (value: string) => readonly ["property", string]; // (No @packageDocumentation comment for this package) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/add-meta-elements.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/add-meta-elements.ts deleted file mode 100644 index f45f8811..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/add-meta-elements.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TestBed } from '@angular/core/testing' -import { Meta, MetaDefinition } from '@angular/platform-browser' - -export const addMetaElements = (tags: ReadonlyArray) => - TestBed.inject(Meta).addTags(tags as MetaDefinition[]) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/clear-metas-after-each.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/clear-metas-after-each.ts deleted file mode 100644 index e103ce5a..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/clear-metas-after-each.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TestBed } from '@angular/core/testing' -import { Meta } from '@angular/platform-browser' - -export const clearMetasAfterEach = (attributeSelector: string) => { - afterEach(() => { - TestBed.inject(Meta) - .getTags(attributeSelector) - .forEach((tag) => tag.remove()) - }) -} diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/get-meta-elements-by-selector.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/get-meta-elements-by-selector.ts deleted file mode 100644 index 5452a321..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/get-meta-elements-by-selector.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { TestBed } from '@angular/core/testing' -import { Meta } from '@angular/platform-browser' - -export const getMetaElementsBySelector = (attrSelector: string) => - TestBed.inject(Meta).getTags(attrSelector) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/html-attributes-to-json.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/html-attributes-to-json.ts deleted file mode 100644 index 35d88dc4..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/__tests__/html-attributes-to-json.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const htmlAttributesToJson = (attributes: NamedNodeMap): object => - [...Array(attributes.length).keys()] - .map((index) => attributes.item(index)) - .map((item) => (item ? { [item.name]: item.value } : {})) - .reduce((acc, curr) => ({ ...acc, ...curr }), {}) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts index 2646d760..14fc4608 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts @@ -1,15 +1,8 @@ -// v2 export { - ngxMetaElementSetter, - NgxMetaElementSetter, -} from './ngx-meta-element-setter' -export { - ngxMetaElementsSetter, - NgxMetaElementsSetter, -} from './ngx-meta-elements-setter' + NgxMetaElementsService, + NgxMetaElementNameAttribute, +} from './ngx-meta-elements.service' export { NgxMetaElementAttributes } from './ngx-meta-element-attributes' -export { - withNameAttribute, - withPropertyAttribute, - withContentAttribute, -} from './meta-elements-helpers' +export { withNameAttribute } from './with-name-attribute' +export { withPropertyAttribute } from './with-property-attribute' +export { withContentAttribute } from './with-content-attribute' diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/meta-elements-helpers.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/meta-elements-helpers.ts deleted file mode 100644 index ca458dff..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/meta-elements-helpers.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' - -/** - * Utility function to specify a `` element. - * - * See {@link NgxMetaElementSetter} for examples. - * - * @param value - Value for the `name` attribute of the `` element - * - * @alpha - */ -export const withNameAttribute = (value: string) => ['name', value] as const -/** - * Utility function to specify a `` element. - * - * See {@link NgxMetaElementSetter} for examples. - * - * @param value - Value for the `property` attribute of the `` element - * - * @alpha - */ -export const withPropertyAttribute = (value: string) => ['property', value] - -/** - * Utility function to create an {@link NgxMetaElementAttributes} specifying the `content` attribute to the - * given `value`. Unless given `value` is `null` or `undefined`. In that case, `undefined` is returned. - * - * See {@link NgxMetaElementSetter} for examples. - * - * @param content - Value for the `property` attribute of the `` element - * - * @alpha - */ -export const withContentAttribute = ( - content: string | null | undefined, -): NgxMetaElementAttributes | undefined => (content ? { content } : undefined) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts deleted file mode 100644 index 231e5d99..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.spec.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { TestBed } from '@angular/core/testing' -import { - ngxMetaElementSetter, - NgxMetaElementSetter, -} from './ngx-meta-element-setter' -import { - withContentAttribute, - withNameAttribute, -} from './meta-elements-helpers' -import { htmlAttributesToJson } from './__tests__/html-attributes-to-json' -import { getMetaElementsBySelector } from './__tests__/get-meta-elements-by-selector' -import { clearMetasAfterEach } from './__tests__/clear-metas-after-each' -import { addMetaElements } from './__tests__/add-meta-elements' - -describe('Meta element setter', () => { - const dummyMetaNameAttribute = withNameAttribute('dummy') - const dummyMetaAttributeSelector = 'name="dummy"' - const dummyMetaContentAttribute = withContentAttribute('dummy') - const dummyMetaAttributes = { name: 'dummy', content: 'dummy' } - - clearMetasAfterEach(dummyMetaAttributeSelector) - - describe('when no element exists yet', () => { - describe('when no content is provided', () => { - it('should not create the meta element', () => { - const sut = makeSut() - - sut(dummyMetaNameAttribute, undefined) - - const elements = getMetaElementsBySelector(dummyMetaAttributeSelector) - expect(elements).toHaveSize(0) - }) - }) - - describe('when content is provided', () => { - it('should create the meta element', () => { - const sut = makeSut() - - sut(dummyMetaNameAttribute, dummyMetaContentAttribute) - - const elements = getMetaElementsBySelector(dummyMetaAttributeSelector) - expect(elements).toHaveSize(1) - const element = elements[0] - expect(htmlAttributesToJson(element.attributes)).toEqual( - dummyMetaAttributes, - ) - }) - }) - }) - - describe('when an element already exists', () => { - let sut: NgxMetaElementSetter - - beforeEach(() => { - sut = makeSut() - addMetaElements([dummyMetaAttributes]) - expect(getMetaElementsBySelector(dummyMetaAttributeSelector)) - .withContext('test setup: element should exist') - .toHaveSize(1) - }) - - describe('when no content is provided', () => { - it('should remove the element', () => { - sut(dummyMetaNameAttribute, undefined) - - expect( - getMetaElementsBySelector(dummyMetaAttributeSelector), - ).toHaveSize(0) - }) - }) - - describe('when content is provided', () => { - const anotherContent = 'another-dummy-content' - - it('should update the element', () => { - sut(dummyMetaNameAttribute, withContentAttribute(anotherContent)) - - const elements = getMetaElementsBySelector(dummyMetaAttributeSelector) - expect(elements).toHaveSize(1) - const element = elements[0] - expect(htmlAttributesToJson(element.attributes)).toEqual({ - ...dummyMetaAttributes, - content: anotherContent, - }) - }) - }) - }) - - describe('with content attribute utility function', () => { - const sut = withContentAttribute - - describe('when no content is provided', () => { - const TEST_CASES = [null, undefined] - - TEST_CASES.forEach((testCase) => { - describe(`like when ${testCase}`, () => { - it('should return nothing', () => { - expect(sut(testCase)).toBeUndefined() - }) - }) - }) - }) - - describe('when content is provided', () => { - const dummyContent = 'dummy' - - it('should return the content value inside the content key', () => { - expect(sut(dummyContent)).toEqual({ content: dummyContent }) - }) - }) - }) -}) - -const makeSut = (): NgxMetaElementSetter => { - TestBed.configureTestingModule({}) - return TestBed.inject(ngxMetaElementSetter()) -} diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts deleted file mode 100644 index 9352e9d3..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-setter.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { inject } from '@angular/core' -import { Meta } from '@angular/platform-browser' -import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' -import { _LazyInjectionToken, _makeInjectionToken } from '../../utils' - -/** - * @alpha - */ -export const ngxMetaElementSetter: _LazyInjectionToken< - NgxMetaElementSetter -> = () => - _makeInjectionToken(ngDevMode ? 'Meta element setter' : 'MES', () => { - const meta = inject(Meta) - return (nameAttribute, content) => { - const [nameAttributeName, nameAttributeValue] = nameAttribute - const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` - /* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */ - if (!content) { - meta.removeTag(attrSelector) - return - } - meta.updateTag( - { [nameAttributeName]: nameAttributeValue, ...content }, - attrSelector, - ) - } - }) - -/** - * @alpha - */ -export type NgxMetaElementSetter = ( - nameAttribute: readonly [name: string, value: string], - content: NgxMetaElementAttributes | undefined, -) => void diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts deleted file mode 100644 index 6f7e0a39..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { - withContentAttribute, - withNameAttribute, -} from './meta-elements-helpers' -import { getMetaElementsBySelector } from './__tests__/get-meta-elements-by-selector' -import { TestBed } from '@angular/core/testing' -import { - ngxMetaElementsSetter, - NgxMetaElementsSetter, -} from './ngx-meta-elements-setter' -import { htmlAttributesToJson } from './__tests__/html-attributes-to-json' -import { clearMetasAfterEach } from './__tests__/clear-metas-after-each' -import { addMetaElements } from './__tests__/add-meta-elements' - -describe('Meta elements setter', () => { - const dummyMetaNameAttribute = withNameAttribute('dummy') - const dummyMetaAttributeSelector = 'name="dummy"' - const dummyMetaContentAttribute = withContentAttribute('dummy') - const dummyMetaAttributes = { name: 'dummy', content: 'dummy' } - const anotherDummyMetaContentAttribute = withContentAttribute('another-dummy') - const anotherDummyMetaAttributes = { name: 'dummy', content: 'another-dummy' } - - clearMetasAfterEach(dummyMetaAttributeSelector) - - describe('when no elements exist yet', () => { - describe('when no contents are provided', () => { - it('should not create any element', () => { - const sut = makeSut() - - sut(dummyMetaNameAttribute, []) - - expect( - getMetaElementsBySelector(dummyMetaAttributeSelector), - ).toHaveSize(0) - }) - }) - - describe('when contents are provided', () => { - it('should create an element for each one', () => { - const sut = makeSut() - sut(dummyMetaNameAttribute, [ - dummyMetaContentAttribute, - anotherDummyMetaContentAttribute, - ]) - - const elements = getMetaElementsBySelector(dummyMetaAttributeSelector) - expect( - elements.map((e) => e.attributes).map(htmlAttributesToJson), - ).toEqual([dummyMetaAttributes, anotherDummyMetaAttributes]) - }) - }) - }) - - describe('when elements exist already', () => { - let sut: NgxMetaElementsSetter - - beforeEach(() => { - sut = makeSut() - - const dummyNameAttribute = { - [dummyMetaNameAttribute[0]]: dummyMetaNameAttribute[1], - } - addMetaElements([ - { - ...dummyNameAttribute, - content: 'existing-content-1', - }, - { - ...dummyNameAttribute, - content: 'existing-content-2', - }, - ]) - expect(getMetaElementsBySelector(dummyMetaAttributeSelector)) - .withContext('test setup: two elements should exist') - .toHaveSize(2) - }) - - describe('when no contents are provided', () => { - it('should remove them all', () => { - sut(dummyMetaNameAttribute, []) - - expect( - getMetaElementsBySelector(dummyMetaAttributeSelector), - ).toHaveSize(0) - }) - }) - - describe('when contents are provided', () => { - it('should replace existing elements', () => { - sut(dummyMetaNameAttribute, [ - dummyMetaContentAttribute, - anotherDummyMetaContentAttribute, - ]) - - expect( - getMetaElementsBySelector(dummyMetaAttributeSelector) - .map((e) => e.attributes) - .map(htmlAttributesToJson), - ).toEqual([dummyMetaAttributes, anotherDummyMetaAttributes]) - }) - }) - }) -}) - -const makeSut = () => { - TestBed.configureTestingModule({}) - return TestBed.inject(ngxMetaElementsSetter()) -} diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts deleted file mode 100644 index 30d7ec37..00000000 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements-setter.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { inject } from '@angular/core' -import { Meta } from '@angular/platform-browser' -import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' -import { _LazyInjectionToken, _makeInjectionToken } from '../../utils' - -/** - * @alpha - */ -export const ngxMetaElementsSetter: _LazyInjectionToken< - NgxMetaElementsSetter -> = () => - _makeInjectionToken(ngDevMode ? 'Meta elements setter' : 'MEsS', () => { - const meta = inject(Meta) - return (nameAttribute, contents) => { - const [nameAttributeName, nameAttributeValue] = nameAttribute - const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` - meta.getTags(attrSelector).forEach((tag) => tag.remove()) - meta.addTags( - contents.map((content) => ({ - [nameAttributeName]: nameAttributeValue, - ...content, - })), - ) - } - }) - -/** - * @alpha - */ -export type NgxMetaElementsSetter = ( - nameAttribute: readonly [name: string, value: string], - contents: ReadonlyArray, -) => void diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.spec.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.spec.ts new file mode 100644 index 00000000..6490e0b0 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.spec.ts @@ -0,0 +1,172 @@ +import { TestBed } from '@angular/core/testing' +import { NgxMetaElementsService } from './ngx-meta-elements.service' +import { withNameAttribute } from './with-name-attribute' +import { withContentAttribute } from './with-content-attribute' +import { Meta, MetaDefinition } from '@angular/platform-browser' + +describe('Meta element service', () => { + const dummyMetaNameAttribute = withNameAttribute('dummy') + const dummyMetaNameAttributeKeyValue = { name: 'dummy' } + const dummyMetaContentAttribute = withContentAttribute('dummy') + const dummyMetaAttributes = { + ...dummyMetaNameAttributeKeyValue, + content: 'dummy', + } + + const anotherDummyMetaContentAttribute = withContentAttribute('another-dummy') + const anotherDummyMetaAttributes = { + ...dummyMetaNameAttributeKeyValue, + content: 'another-dummy', + } + + const yetAnotherDummyMetaContentAttribute = + withContentAttribute('yet-another-dummy') + const yetAnotherDummyMetaAttributes = { + ...dummyMetaNameAttributeKeyValue, + content: 'yet-another-dummy', + } + + const getDummyMetaElements = () => + TestBed.inject(Meta).getTags('name="dummy"') + + afterEach(() => { + getDummyMetaElements().forEach((element) => element.remove()) + }) + + describe('when no elements exist', () => { + describe('when no contents are provided', () => { + const TEST_CASES = [ + [[], 'empty array'], + [undefined, 'undefined'], + ] as const + + TEST_CASES.forEach(([testCase, testCaseName]) => { + describe(`like when ${testCaseName}`, () => { + it('should not create any element', () => { + const sut = makeSut() + + sut.set(dummyMetaNameAttribute, testCase) + + expect(getDummyMetaElements()).toHaveSize(0) + }) + }) + }) + }) + + describe('when contents are provided', () => { + describe('when a single content is provided', () => { + it('should create the element', () => { + const sut = makeSut() + sut.set(dummyMetaNameAttribute, dummyMetaContentAttribute) + + const elements = getDummyMetaElements() + expect(elements.length).toBe(1) + const element = elements[0] + expect(htmlAttributesToJson(element.attributes)).toEqual( + dummyMetaAttributes, + ) + }) + }) + + describe('when multiple contents are provided', () => { + it('should create an element for each one', () => { + const sut = makeSut() + sut.set(dummyMetaNameAttribute, [ + dummyMetaContentAttribute, + anotherDummyMetaContentAttribute, + ]) + + const elements = getDummyMetaElements() + expect( + elements.map((e) => e.attributes).map(htmlAttributesToJson), + ).toEqual([dummyMetaAttributes, anotherDummyMetaAttributes]) + }) + }) + }) + }) + + describe('when elements exist', () => { + let sut: NgxMetaElementsService + + beforeEach(() => { + sut = makeSut() + + const dummyNameAttribute = { + [dummyMetaNameAttribute[0]]: dummyMetaNameAttribute[1], + } + TestBed.inject(Meta).addTags([ + { + ...dummyNameAttribute, + content: 'existing-content-1', + }, + { + ...dummyNameAttribute, + content: 'existing-content-2', + }, + ] as MetaDefinition[]) + expect(getDummyMetaElements()) + .withContext('test setup: two elements should exist') + .toHaveSize(2) + }) + + describe('when no contents are provided', () => { + const TEST_CASES = [ + [[], 'empty array'], + [undefined, 'undefined'], + ] as const + + TEST_CASES.forEach(([testCase, testCaseName]) => { + describe(`like when ${testCaseName}`, () => { + it('should remove them all', () => { + sut.set(dummyMetaNameAttribute, testCase) + + expect(getDummyMetaElements()).toHaveSize(0) + }) + }) + }) + }) + + describe('when contents are provided', () => { + describe('when a single content is provided', () => { + it('should remove existing elements and create the new one', () => { + sut.set(dummyMetaNameAttribute, dummyMetaContentAttribute) + + const elements = getDummyMetaElements() + expect(elements.length).toBe(1) + const element = elements[0] + expect(htmlAttributesToJson(element.attributes)).toEqual( + dummyMetaAttributes, + ) + }) + }) + + describe('when multiple contents are provided', () => { + it('should remove existing elements and create new ones', () => { + sut.set(dummyMetaNameAttribute, [ + dummyMetaContentAttribute, + anotherDummyMetaContentAttribute, + yetAnotherDummyMetaContentAttribute, + ]) + + expect( + getDummyMetaElements() + .map((e) => e.attributes) + .map(htmlAttributesToJson), + ).toEqual([ + dummyMetaAttributes, + anotherDummyMetaAttributes, + yetAnotherDummyMetaAttributes, + ]) + }) + }) + }) + }) +}) + +const makeSut = () => TestBed.inject(NgxMetaElementsService) + +export const htmlAttributesToJson = (attributes: NamedNodeMap): object => + [...Array(attributes.length).keys()] + .map((index) => attributes.item(index)) + .map((item) => (item ? { [item.name]: item.value } : {})) + .reduce((acc, curr) => ({ ...acc, ...curr }), {}) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts new file mode 100644 index 00000000..de1e36ea --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts @@ -0,0 +1,48 @@ +import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' +import { Meta } from '@angular/platform-browser' +import { Injectable } from '@angular/core' + +/** + * Manages `` elements inside ``. + * + * @alpha + */ +@Injectable({ providedIn: 'root' }) +export class NgxMetaElementsService { + constructor(private meta: Meta) {} + + set( + nameAttribute: NgxMetaElementNameAttribute, + content: + | ReadonlyArray + | NgxMetaElementAttributes + | undefined, + ): void { + const [nameAttributeName, nameAttributeValue] = nameAttribute + const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` + this.meta.getTags(attrSelector).forEach((tag) => tag.remove()) + if (!content) { + return + } + const contents = (Array.isArray as isContentsArray)(content) + ? content + : [content] + this.meta.addTags( + contents.map((content) => ({ + [nameAttributeName]: nameAttributeValue, + ...content, + })), + ) + } +} + +type isContentsArray = ( + contentOrContents: + | ReadonlyArray + | NgxMetaElementAttributes, +) => contentOrContents is ReadonlyArray + +/** + * @alpha + */ +export type NgxMetaElementNameAttribute = readonly [name: string, value: string] diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts new file mode 100644 index 00000000..648d27a4 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts @@ -0,0 +1,19 @@ +import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' + +/** + * Creates an {@link NgxMetaElementAttributes} object specifying the `content` attribute to the + * given `value`. + * + * Unless given `value` is `null` or `undefined`. In that case, `undefined` is returned. + * + * See {@link NgxMetaElementsService.set} for examples. + * + * @param content - Value for the `property` attribute of the `` element + * + * @alpha + */ +export const withContentAttribute = ((content: string | null | undefined) => + content ? { content } : undefined) as { + (content: null | undefined): undefined + (content: string): NgxMetaElementAttributes +} diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-name-attribute.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-name-attribute.ts new file mode 100644 index 00000000..626b7844 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-name-attribute.ts @@ -0,0 +1,13 @@ +import { NgxMetaElementNameAttribute } from './ngx-meta-elements.service' + +/** + * Utility function to specify a `` element. + * + * See {@link NgxMetaElementsService.set} for examples. + * + * @param value - Value for the `name` attribute of the `` element + * + * @alpha + */ +export const withNameAttribute = (value: string) => + ['name', value] as const satisfies NgxMetaElementNameAttribute diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts new file mode 100644 index 00000000..8b8ab2d9 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts @@ -0,0 +1,13 @@ +import { NgxMetaElementNameAttribute } from './ngx-meta-elements.service' + +/** + * Utility function to specify a `` element. + * + * See {@link NgxMetaElementsService.set} for examples. + * + * @param value - Value for the `property` attribute of the `` element + * + * @alpha + */ +export const withPropertyAttribute = (value: string) => + ['property', value] as const satisfies NgxMetaElementNameAttribute From ffe99d908fa8e840492f84a98695d81384aa4f8f Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 14:52:56 +0200 Subject: [PATCH 06/13] test: add content helper test & ignores for coverage --- .../v2/ngx-meta-elements.service.ts | 1 + .../v2/with-content-attribute.spec.ts | 24 +++++++++++++++++++ .../v2/with-property-attribute.ts | 1 + 3 files changed, 26 insertions(+) create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.spec.ts diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts index de1e36ea..815e6970 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts @@ -21,6 +21,7 @@ export class NgxMetaElementsService { const [nameAttributeName, nameAttributeValue] = nameAttribute const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` this.meta.getTags(attrSelector).forEach((tag) => tag.remove()) + /* istanbul ignore next https://github.com/istanbuljs/istanbuljs/issues/719 */ if (!content) { return } diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.spec.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.spec.ts new file mode 100644 index 00000000..ed4da216 --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.spec.ts @@ -0,0 +1,24 @@ +import { withContentAttribute } from './with-content-attribute' + +describe('with content attribute', () => { + const sut = withContentAttribute + + describe('when no content is provided', () => { + const TEST_CASES = [null, undefined] + + TEST_CASES.forEach((testCase) => { + describe(`like when ${testCase}`, () => { + it('should return undefined', () => { + expect(sut(testCase)).toBeUndefined() + }) + }) + }) + }) + + describe('when content is provided', () => { + it('should return an object containing the given content in the content key', () => { + const content = 'dummy' + expect(sut(content)).toEqual({ content }) + }) + }) +}) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts index 8b8ab2d9..efa11fa3 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts @@ -9,5 +9,6 @@ import { NgxMetaElementNameAttribute } from './ngx-meta-elements.service' * * @alpha */ +/* istanbul ignore next - will be used in next PRs */ export const withPropertyAttribute = (value: string) => ['property', value] as const satisfies NgxMetaElementNameAttribute From bf0ffe725db761bcb61fa67e5b73e634e9056958 Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 17:31:43 +0200 Subject: [PATCH 07/13] docs: move `http-equiv` docs to parent type --- .../meta-elements/v2/ngx-meta-element-attributes.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts index 1220a17c..037c10bc 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts @@ -1,21 +1,18 @@ /** * Models a `` element HTML's attributes as a key / value map. * - * Inspired on Angular's {@link https://angular.dev/api/platform-browser/MetaDefinition/ | MetaDefinition} + * Almost equivalent to Angular's {@link https://angular.dev/api/platform-browser/MetaDefinition/ | MetaDefinition} * - * Only difference is `http-equiv` property. + * Only difference is `http-equiv` property. In an Angular's + * {@link https://angular.dev/api/platform-browser/MetaDefinition/ | MetaDefinition}, `httpEquiv` would also be + * accepted. This way there's no need to quote the key property. + * But without `httpEquiv` there's no need to map attribute names. So one bit of code less. * * @alpha */ export type NgxMetaElementAttributes = Partial<{ charset: string content: string - /** - * In an Angular's {@link https://angular.dev/api/platform-browser/MetaDefinition/ | MetaDefinition}, - * `httpEquiv` would also be accepted. This way there's no need to quote the key property. - * - * But without `httpEquiv` there's no need to map attribute names. So one more bit of code less around. - */ 'http-equiv': string id: string itemprop: string From de25a392e9caf9b1706f4487e0f714945395ef54 Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 17:32:48 +0200 Subject: [PATCH 08/13] chore: add media attribute to meta element type --- .../src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts index 037c10bc..79b89403 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts @@ -20,6 +20,7 @@ export type NgxMetaElementAttributes = Partial<{ property: string scheme: string url: string + media: string }> & { [key: string]: string } From 772ffe02cb63ba6723ae2e5e8ab76dd8055c9ab0 Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 18:12:28 +0200 Subject: [PATCH 09/13] feat: add extras to with content attribute helper --- .../ngx-meta/api-extractor/ngx-meta.api.md | 6 ++-- .../v2/with-content-attribute.spec.ts | 29 +++++++++++++++---- .../v2/with-content-attribute.ts | 10 ++++--- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/projects/ngx-meta/api-extractor/ngx-meta.api.md b/projects/ngx-meta/api-extractor/ngx-meta.api.md index 4b21a186..e2f7de36 100644 --- a/projects/ngx-meta/api-extractor/ngx-meta.api.md +++ b/projects/ngx-meta/api-extractor/ngx-meta.api.md @@ -210,6 +210,7 @@ export type NgxMetaElementAttributes = Partial<{ property: string; scheme: string; url: string; + media: string; }> & { [key: string]: string; }; @@ -220,7 +221,6 @@ export type NgxMetaElementNameAttribute = readonly [name: string, value: string] // @alpha export class NgxMetaElementsService { constructor(meta: Meta); - // (undocumented) set(nameAttribute: NgxMetaElementNameAttribute, content: ReadonlyArray | NgxMetaElementAttributes | undefined): void; } @@ -581,8 +581,8 @@ export const _urlResolver: _LazyInjectionToken<_UrlResolver>; // @alpha export const withContentAttribute: { - (content: null | undefined): undefined; - (content: string): NgxMetaElementAttributes; + (content: null | undefined, extras?: NgxMetaElementAttributes): undefined; + (content: string, extras?: NgxMetaElementAttributes): NgxMetaElementAttributes; }; // @alpha diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.spec.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.spec.ts index ed4da216..25bd6f6d 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.spec.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.spec.ts @@ -1,24 +1,43 @@ import { withContentAttribute } from './with-content-attribute' +import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' describe('with content attribute', () => { const sut = withContentAttribute + const extras = { dummy: 'dummy' } satisfies NgxMetaElementAttributes describe('when no content is provided', () => { const TEST_CASES = [null, undefined] TEST_CASES.forEach((testCase) => { describe(`like when ${testCase}`, () => { - it('should return undefined', () => { - expect(sut(testCase)).toBeUndefined() + describe('when not providing extras', () => { + it('should return undefined', () => { + expect(sut(testCase)).toBeUndefined() + }) + }) + + describe('when providing extras', () => { + it('should return undefined', () => { + expect(sut(testCase, extras)).toBeUndefined() + }) }) }) }) }) describe('when content is provided', () => { - it('should return an object containing the given content in the content key', () => { - const content = 'dummy' - expect(sut(content)).toEqual({ content }) + const content = 'dummy' + + describe('when not providing extras', () => { + it('should return an object containing the given content in the content key', () => { + expect(sut(content)).toEqual({ content }) + }) + }) + + describe('when providing extras', () => { + it('should return an object containing the given content in the content key plus extras', () => { + expect(sut(content, extras)).toEqual({ content, ...extras }) + }) }) }) }) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts index 648d27a4..0c2c314e 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts @@ -12,8 +12,10 @@ import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' * * @alpha */ -export const withContentAttribute = ((content: string | null | undefined) => - content ? { content } : undefined) as { - (content: null | undefined): undefined - (content: string): NgxMetaElementAttributes +export const withContentAttribute = (( + content: string | null | undefined, + extras?: NgxMetaElementAttributes, +) => (content ? { content, ...extras } : undefined)) as { + (content: null | undefined, extras?: NgxMetaElementAttributes): undefined + (content: string, extras?: NgxMetaElementAttributes): NgxMetaElementAttributes } From 211b905aafddc22eae5699f4a5360556d9328085 Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 18:23:26 +0200 Subject: [PATCH 10/13] docs: add docs for new meta elements service --- .../v2/ngx-meta-element-attributes.ts | 2 +- .../v2/ngx-meta-elements.service.ts | 78 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts index 79b89403..e288d146 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-attributes.ts @@ -1,5 +1,5 @@ /** - * Models a `` element HTML's attributes as a key / value map. + * Models a `` element HTML's attributes as a key / value JSON object. * * Almost equivalent to Angular's {@link https://angular.dev/api/platform-browser/MetaDefinition/ | MetaDefinition} * diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts index 815e6970..ea2b3132 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts @@ -5,12 +5,90 @@ import { Injectable } from '@angular/core' /** * Manages `` elements inside ``. * + * See {@link NgxMetaElementsService.set} + * * @alpha */ @Injectable({ providedIn: 'root' }) export class NgxMetaElementsService { constructor(private meta: Meta) {} + /** + * Creates, updates or removes some kind of `` elements inside `` in a declarative fashion. + * + * Kind of `` elements to manage are identified by an HTML attribute providing its metadata name. + * For instance, to manage description metadata elements (``) on the page, the + * `name` attribute with `description` value identifies them. + * + * Then, contents for those can be specified. In the shape of a key/value JSON object declaring each element's + * additional attributes. Mainly `content` named attributes. See {@link NgxMetaElementAttributes}. + * If no contents are provided, all `` elements of that kind will be removed. + * An array of contents may be given to create multiple `` elements with same kind. + * + * @example + * Setting `` + * + * ```typescript + * ngxMetaElementsService.set( + * withNameAttribute('description'), // same as `['name', 'description']` + * withContent('Cool page'), // same as `{ content: 'Cool page' }` + * ) + * ``` + * + * Utility functions {@link withNameAttribute} and {@link withContentAttribute} help creating the + * name attribute identifying the kind of meta elements and the contents to provide for it. + * + * {@link withContentAttribute} helps to create the attributes key / value object. + * + * Removing any `` existing elements + * + * ```typescript + * ngxMetaElementsService.set( + * withNameAttribute('description'), // same as `['name', 'description']` + * undefined, // same as `withContent(undefined)` + * ) + * ``` + * + * Setting many `` elements + * + * ```typescript + * ngxMetaElementsService.set( + * withNameAttribute('theme-color'), // same as `['name', 'theme-color']` + * [ + * withContent('darkblue', { media: "(prefers-color-scheme: dark)" }), + * withContent('lightblue') // same as `{ content: 'lightblue' }` + * ] + * ) + * ``` + * + * Removing any `` existing elements + * + * ```typescript + * ngxMetaElementsService.set( + * withNameAttribute('theme-color'), // same as `['name', 'theme-color']` + * [], // `undefined` is valid too + * ) + * ``` + * + * Attribute name helpers: + * + * - {@link withNameAttribute} + * + * - {@link withPropertyAttribute} + * + * Content helpers: + * + * - {@link withContentAttribute} + * + * @param nameAttribute - Attribute use to identify which kind of `` elements to manage. + * As an array with the attribute name in first position and attribute value in second one. + * Utility functions exist to generate arrays for common name attributes. + * See {@link withNameAttribute} and {@link withPropertyAttribute} helpers to create those + * arrays without repeating attribute names around. + * + * @param content - Content(s) attributes to set for this `` elements kind. + * Or the lack of them to remove all `` elements of this kind. + */ set( nameAttribute: NgxMetaElementNameAttribute, content: From a008aebf12afede90a8a58171f18b0a8c2be7dd1 Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 19:01:27 +0200 Subject: [PATCH 11/13] docs: add class name, method and function docs --- .../ngx-meta/api-extractor/ngx-meta.api.md | 8 +++-- .../src/core/src/meta-elements/v2/index.ts | 6 ++-- .../v2/ngx-meta-element-name-attribute.ts | 6 ++++ .../v2/ngx-meta-elements.service.ts | 30 +++++++------------ .../v2/with-content-attribute.ts | 5 ++-- .../meta-elements/v2/with-name-attribute.ts | 9 ++---- .../v2/with-property-attribute.ts | 8 ++--- 7 files changed, 33 insertions(+), 39 deletions(-) create mode 100644 projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-name-attribute.ts diff --git a/projects/ngx-meta/api-extractor/ngx-meta.api.md b/projects/ngx-meta/api-extractor/ngx-meta.api.md index e2f7de36..600ddccb 100644 --- a/projects/ngx-meta/api-extractor/ngx-meta.api.md +++ b/projects/ngx-meta/api-extractor/ngx-meta.api.md @@ -215,13 +215,15 @@ export type NgxMetaElementAttributes = Partial<{ [key: string]: string; }; -// @alpha (undocumented) +// @alpha export type NgxMetaElementNameAttribute = readonly [name: string, value: string]; -// @alpha +// @beta export class NgxMetaElementsService { constructor(meta: Meta); - set(nameAttribute: NgxMetaElementNameAttribute, content: ReadonlyArray | NgxMetaElementAttributes | undefined): void; + // Warning: (ae-incompatible-release-tags) The symbol "set" is marked as @beta, but its signature references "NgxMetaElementNameAttribute" which is marked as @alpha + // Warning: (ae-incompatible-release-tags) The symbol "set" is marked as @beta, but its signature references "NgxMetaElementAttributes" which is marked as @alpha + set(nameAttribute: NgxMetaElementNameAttribute, content: NgxMetaElementAttributes[] | NgxMetaElementAttributes | undefined): void; } // @public diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts index 14fc4608..5d388e5b 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/index.ts @@ -1,7 +1,5 @@ -export { - NgxMetaElementsService, - NgxMetaElementNameAttribute, -} from './ngx-meta-elements.service' +export { NgxMetaElementsService } from './ngx-meta-elements.service' +export { NgxMetaElementNameAttribute } from './ngx-meta-element-name-attribute' export { NgxMetaElementAttributes } from './ngx-meta-element-attributes' export { withNameAttribute } from './with-name-attribute' export { withPropertyAttribute } from './with-property-attribute' diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-name-attribute.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-name-attribute.ts new file mode 100644 index 00000000..c41cfdbd --- /dev/null +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-element-name-attribute.ts @@ -0,0 +1,6 @@ +/** + * See {@link NgxMetaElementsService.set} + * + * @alpha + */ +export type NgxMetaElementNameAttribute = readonly [name: string, value: string] diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts index ea2b3132..124c8843 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts @@ -1,13 +1,12 @@ import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' import { Meta } from '@angular/platform-browser' import { Injectable } from '@angular/core' +import { NgxMetaElementNameAttribute } from './ngx-meta-element-name-attribute' /** - * Manages `` elements inside ``. + * Manages `` elements inside `` * - * See {@link NgxMetaElementsService.set} - * - * @alpha + * @beta */ @Injectable({ providedIn: 'root' }) export class NgxMetaElementsService { @@ -30,8 +29,8 @@ export class NgxMetaElementsService { * * ```typescript * ngxMetaElementsService.set( - * withNameAttribute('description'), // same as `['name', 'description']` - * withContent('Cool page'), // same as `{ content: 'Cool page' }` + * withNameAttribute('description'), // same as `['name','description']` + * withContent('Cool page'), // same as `{content:'Cool page'}` * ) * ``` * @@ -44,7 +43,7 @@ export class NgxMetaElementsService { * * ```typescript * ngxMetaElementsService.set( - * withNameAttribute('description'), // same as `['name', 'description']` + * withNameAttribute('description'), // same as `['name','description']` * undefined, // same as `withContent(undefined)` * ) * ``` @@ -53,10 +52,10 @@ export class NgxMetaElementsService { * * ```typescript * ngxMetaElementsService.set( - * withNameAttribute('theme-color'), // same as `['name', 'theme-color']` + * withNameAttribute('theme-color'), // same as `['name','theme-color']` * [ * withContent('darkblue', { media: "(prefers-color-scheme: dark)" }), - * withContent('lightblue') // same as `{ content: 'lightblue' }` + * withContent('lightblue') // same as `{content:'lightblue'}` * ] * ) * ``` @@ -65,7 +64,7 @@ export class NgxMetaElementsService { * * ```typescript * ngxMetaElementsService.set( - * withNameAttribute('theme-color'), // same as `['name', 'theme-color']` + * withNameAttribute('theme-color'), // same as `['name','theme-color']` * [], // `undefined` is valid too * ) * ``` @@ -88,13 +87,11 @@ export class NgxMetaElementsService { * * @param content - Content(s) attributes to set for this `` elements kind. * Or the lack of them to remove all `` elements of this kind. + * See {@link withContentAttribute} helper for creating content objects. */ set( nameAttribute: NgxMetaElementNameAttribute, - content: - | ReadonlyArray - | NgxMetaElementAttributes - | undefined, + content: NgxMetaElementAttributes[] | NgxMetaElementAttributes | undefined, ): void { const [nameAttributeName, nameAttributeValue] = nameAttribute const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` @@ -120,8 +117,3 @@ type isContentsArray = ( | ReadonlyArray | NgxMetaElementAttributes, ) => contentOrContents is ReadonlyArray - -/** - * @alpha - */ -export type NgxMetaElementNameAttribute = readonly [name: string, value: string] diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts index 0c2c314e..6a2ce790 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-content-attribute.ts @@ -2,13 +2,14 @@ import { NgxMetaElementAttributes } from './ngx-meta-element-attributes' /** * Creates an {@link NgxMetaElementAttributes} object specifying the `content` attribute to the - * given `value`. + * given `value`. Plus optional `extras`. * * Unless given `value` is `null` or `undefined`. In that case, `undefined` is returned. * - * See {@link NgxMetaElementsService.set} for examples. + * See {@link NgxMetaElementsService.set} * * @param content - Value for the `property` attribute of the `` element + * @param extras - Extra attributes to include in the object if `content` is defined. * * @alpha */ diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-name-attribute.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-name-attribute.ts index 626b7844..03b917b3 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/with-name-attribute.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-name-attribute.ts @@ -1,13 +1,10 @@ -import { NgxMetaElementNameAttribute } from './ngx-meta-elements.service' - /** - * Utility function to specify a `` element. + * Creates an attribute name/value identifying a `` element kind. * - * See {@link NgxMetaElementsService.set} for examples. + * See {@link NgxMetaElementsService.set}. * * @param value - Value for the `name` attribute of the `` element * * @alpha */ -export const withNameAttribute = (value: string) => - ['name', value] as const satisfies NgxMetaElementNameAttribute +export const withNameAttribute = (value: string) => ['name', value] as const diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts index efa11fa3..28c38251 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/with-property-attribute.ts @@ -1,9 +1,7 @@ -import { NgxMetaElementNameAttribute } from './ngx-meta-elements.service' - /** - * Utility function to specify a `` element. + * Creates an attribute name/value identifying a `` element kind. * - * See {@link NgxMetaElementsService.set} for examples. + * See {@link NgxMetaElementsService.set}. * * @param value - Value for the `property` attribute of the `` element * @@ -11,4 +9,4 @@ import { NgxMetaElementNameAttribute } from './ngx-meta-elements.service' */ /* istanbul ignore next - will be used in next PRs */ export const withPropertyAttribute = (value: string) => - ['property', value] as const satisfies NgxMetaElementNameAttribute + ['property', value] as const From 59722b40ed21f538fa013e713cd2ef05b6aa0a11 Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 19:21:19 +0200 Subject: [PATCH 12/13] chore: fix readonly type --- projects/ngx-meta/api-extractor/ngx-meta.api.md | 2 +- .../core/src/meta-elements/v2/ngx-meta-elements.service.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/projects/ngx-meta/api-extractor/ngx-meta.api.md b/projects/ngx-meta/api-extractor/ngx-meta.api.md index 600ddccb..28ab5e6c 100644 --- a/projects/ngx-meta/api-extractor/ngx-meta.api.md +++ b/projects/ngx-meta/api-extractor/ngx-meta.api.md @@ -223,7 +223,7 @@ export class NgxMetaElementsService { constructor(meta: Meta); // Warning: (ae-incompatible-release-tags) The symbol "set" is marked as @beta, but its signature references "NgxMetaElementNameAttribute" which is marked as @alpha // Warning: (ae-incompatible-release-tags) The symbol "set" is marked as @beta, but its signature references "NgxMetaElementAttributes" which is marked as @alpha - set(nameAttribute: NgxMetaElementNameAttribute, content: NgxMetaElementAttributes[] | NgxMetaElementAttributes | undefined): void; + set(nameAttribute: NgxMetaElementNameAttribute, content: ReadonlyArray | NgxMetaElementAttributes | undefined): void; } // @public diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts index 124c8843..e95acb76 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts @@ -91,7 +91,10 @@ export class NgxMetaElementsService { */ set( nameAttribute: NgxMetaElementNameAttribute, - content: NgxMetaElementAttributes[] | NgxMetaElementAttributes | undefined, + content: + | ReadonlyArray + | NgxMetaElementAttributes + | undefined, ): void { const [nameAttributeName, nameAttributeValue] = nameAttribute const attrSelector = `${nameAttributeName}="${nameAttributeValue}"` From 97396909182fd671df18d5883bde573b17e11823 Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 10 Oct 2024 19:23:13 +0200 Subject: [PATCH 13/13] docs: add note about alpha state --- .../core/src/meta-elements/v2/ngx-meta-elements.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts index e95acb76..c4b6ea68 100644 --- a/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts +++ b/projects/ngx-meta/src/core/src/meta-elements/v2/ngx-meta-elements.service.ts @@ -6,6 +6,8 @@ import { NgxMetaElementNameAttribute } from './ngx-meta-element-name-attribute' /** * Manages `` elements inside `` * + * API is in alpha state. But appears as beta due a tooling limitation + * * @beta */ @Injectable({ providedIn: 'root' }) @@ -15,6 +17,8 @@ export class NgxMetaElementsService { /** * Creates, updates or removes some kind of `` elements inside `` in a declarative fashion. * + * API is in alpha state. But appears as beta due a tooling limitation + * * Kind of `` elements to manage are identified by an HTML attribute providing its metadata name. * For instance, to manage description metadata elements (``) on the page, the * `name` attribute with `description` value identifies them.