Skip to content

Commit

Permalink
Merge pull request #28588 from Marklb/marklb/fix-non-dot-notation-in-tpl
Browse files Browse the repository at this point in the history
Angular: Fix template props not able to use dot notation
  • Loading branch information
valentinpalkovic authored Jul 24, 2024
2 parents 2f7072b + d40abaa commit 1b2d50a
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
import { Component } from '@angular/core';
import { ArgTypes } from 'storybook/internal/types';
import { describe, it, expect } from 'vitest';
import { computesTemplateSourceFromComponent } from './ComputesTemplateFromComponent';
import {
computesTemplateFromComponent,
computesTemplateSourceFromComponent,
} from './ComputesTemplateFromComponent';
import { ISomeInterface, ButtonAccent, InputComponent } from './__testfixtures__/input.component';

describe('angular template decorator', () => {
it('with props should generate tag with properties', () => {
const component = InputComponent;
const props = {
isDisabled: true,
label: 'Hello world',
accent: ButtonAccent.High,
counter: 4,
'aria-label': 'Hello world',
};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(
`<doc-button [counter]="counter" [accent]="accent" [isDisabled]="isDisabled" [label]="label" [aria-label]="this['aria-label']"></doc-button>`
);
});

it('with props should generate tag with outputs', () => {
const component = InputComponent;
const props = {
isDisabled: true,
label: 'Hello world',
onClick: ($event: any) => {},
'dash-out': ($event: any) => {},
};
const source = computesTemplateFromComponent(component, props);
expect(source).toEqual(
`<doc-button [isDisabled]="isDisabled" [label]="label" (onClick)="onClick($event)" (dash-out)="this['dash-out']($event)"></doc-button>`
);
});
});

describe('angular source decorator', () => {
it('With no props should generate simple tag', () => {
const component = InputComponent;
Expand Down Expand Up @@ -264,18 +298,20 @@ describe('angular source decorator', () => {
const source = computesTemplateSourceFromComponent(component, props, argTypes);
expect(source).toEqual(`<doc-button></doc-button>`);
});

it('With props should generate tag with properties', () => {
const component = InputComponent;
const props = {
isDisabled: true,
label: 'Hello world',
accent: ButtonAccent.High,
counter: 4,
'aria-label': 'Hello world',
};
const argTypes: ArgTypes = {};
const source = computesTemplateSourceFromComponent(component, props, argTypes);
expect(source).toEqual(
`<doc-button [counter]="4" [accent]="'High'" [isDisabled]="true" [label]="'Hello world'"></doc-button>`
`<doc-button [counter]="4" [accent]="'High'" [isDisabled]="true" [label]="'Hello world'" [aria-label]="'Hello world'"></doc-button>`
);
});

Expand All @@ -285,11 +321,12 @@ describe('angular source decorator', () => {
isDisabled: true,
label: 'Hello world',
onClick: ($event: any) => {},
'dash-out': ($event: any) => {},
};
const argTypes: ArgTypes = {};
const source = computesTemplateSourceFromComponent(component, props, argTypes);
expect(source).toEqual(
`<doc-button [isDisabled]="true" [label]="'Hello world'" (onClick)="onClick($event)"></doc-button>`
`<doc-button [isDisabled]="true" [label]="'Hello world'" (onClick)="onClick($event)" (dash-out)="this['dash-out']($event)"></doc-button>`
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ import {
getComponentInputsOutputs,
} from './utils/NgComponentAnalyzer';

/**
* Check if the name matches the criteria for a valid identifier.
* A valid identifier can only contain letters, digits, underscores, or dollar signs.
* It cannot start with a digit.
*/
const isValidIdentifier = (name: string): boolean => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);

/**
* Returns the property name, if it can be accessed with dot notation. If not,
* it returns `this['propertyName']`.
*/
export const formatPropInTemplate = (propertyName: string) =>
isValidIdentifier(propertyName) ? propertyName : `this['${propertyName}']`;

const separateInputsOutputsAttributes = (
ngComponentInputsOutputs: ComponentInputsOutputs,
props: ICollection = {}
Expand Down Expand Up @@ -50,10 +64,12 @@ export const computesTemplateFromComponent = (
);

const templateInputs =
initialInputs.length > 0 ? ` ${initialInputs.map((i) => `[${i}]="${i}"`).join(' ')}` : '';
initialInputs.length > 0
? ` ${initialInputs.map((i) => `[${i}]="${formatPropInTemplate(i)}"`).join(' ')}`
: '';
const templateOutputs =
initialOutputs.length > 0
? ` ${initialOutputs.map((i) => `(${i})="${i}($event)"`).join(' ')}`
? ` ${initialOutputs.map((i) => `(${i})="${formatPropInTemplate(i)}($event)"`).join(' ')}`
: '';

return buildTemplate(
Expand Down Expand Up @@ -137,7 +153,7 @@ export const computesTemplateSourceFromComponent = (
: '';
const templateOutputs =
initialOutputs.length > 0
? ` ${initialOutputs.map((i) => `(${i})="${i}($event)"`).join(' ')}`
? ` ${initialOutputs.map((i) => `(${i})="${formatPropInTemplate(i)}($event)"`).join(' ')}`
: '';

return buildTemplate(ngComponentMetadata.selector, '', templateInputs, templateOutputs);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,13 @@ export class InputComponent<T> {
@Input()
public label: string;

@Input('aria-label') public ariaLabel: string;

/** Specifies some arbitrary object */
@Input() public someDataObject: ISomeInterface;

@Output()
public onClick = new EventEmitter<Event>();

@Output('dash-out') public dashOut = new EventEmitter<any>();
}
6 changes: 6 additions & 0 deletions code/frameworks/angular/src/client/argsToTemplate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,10 @@ describe('argsToTemplate', () => {
const result = argsToTemplate(args, {});
expect(result).toEqual('[input]="input" (event1)="event1($event)"');
});

it('should format for non dot notation', () => {
const args = { 'non-dot': 'Value1', 'dash-out': () => {} };
const result = argsToTemplate(args, {});
expect(result).toEqual('[non-dot]="this[\'non-dot\']" (dash-out)="this[\'dash-out\']($event)"');
});
});
6 changes: 5 additions & 1 deletion code/frameworks/angular/src/client/argsToTemplate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { formatPropInTemplate } from './angular-beta/ComputesTemplateFromComponent';

/**
* Options for controlling the behavior of the argsToTemplate function.
*
Expand Down Expand Up @@ -68,7 +70,9 @@ export function argsToTemplate<A extends Record<string, any>>(
return true;
})
.map(([key, value]) =>
typeof value === 'function' ? `(${key})="${key}($event)"` : `[${key}]="${key}"`
typeof value === 'function'
? `(${key})="${formatPropInTemplate(key)}($event)"`
: `[${key}]="${formatPropInTemplate(key)}"`
)
.join(' ');
}

0 comments on commit 1b2d50a

Please sign in to comment.