Skip to content

Commit

Permalink
chore(cdk): migrate button appearance (#8100)
Browse files Browse the repository at this point in the history
Co-authored-by: Dementii Kalkov <[email protected]>
  • Loading branch information
demkalkov and Dementii Kalkov authored Jul 22, 2024
1 parent 42ce263 commit 9c583df
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
migrateAvatar,
migrateBadge,
migrateBadgedContent,
migrateButtonAppearance,
migrateCheckbox,
migrateExpandable,
migrateFocusable,
Expand Down Expand Up @@ -105,6 +106,7 @@ export function migrateTemplates(fileSystem: DevkitFileSystem, options: TuiSchem
migrateProgressSegmented,
migrateThumbnailCard,
migrateOverscroll,
migrateButtonAppearance,
migrateLabel,
] as const;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './migrate-avatar';
export * from './migrate-badge';
export * from './migrate-badged-content';
export * from './migrate-button-appearance';
export * from './migrate-expandable';
export * from './migrate-focusable';
export * from './migrate-label';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type {UpdateRecorder} from '@angular-devkit/schematics';
import type {DevkitFileSystem} from 'ng-morph';
import type {ElementLocation} from 'parse5/dist/common/token';
import type {Element} from 'parse5/dist/tree-adapters/default';

import {findElementsWithDirective} from '../../../../utils/templates/elements';
import {
getTemplateFromTemplateResource,
getTemplateOffset,
} from '../../../../utils/templates/template-resource';
import type {TemplateResource} from '../../../interfaces';
import {removeAttrs} from '../utils/remove-attrs';

const tuiButtonSelectors = ['tuiButton', 'tuiIconButton'];

const appearanceInputName = 'appearance';
const appearanceInputNameDict = {
[appearanceInputName]: true,
[`[${appearanceInputName}]`]: true,
} as const;

export function migrateButtonAppearance({
resource,
recorder,
fileSystem,
}: {
fileSystem: DevkitFileSystem;
recorder: UpdateRecorder;
resource: TemplateResource;
}): void {
const template = getTemplateFromTemplateResource(resource, fileSystem);
const templateOffset = getTemplateOffset(resource);

const elements = tuiButtonSelectors.flatMap((selector) =>
findElementsWithDirective(template, selector).filter(
({sourceCodeLocation, attrs}) =>
!!sourceCodeLocation &&
attrs.some(({name}) => appearanceInputNameDict[name]),
),
);

if (!elements.length) {
return;
}

const whiteBlockValue = 'whiteblock-active';

elements.forEach(({attrs, sourceCodeLocation}: Element) => {
const whiteBlockActiveAttr = attrs.find(
({value, name}) =>
appearanceInputNameDict[name] &&
(value === whiteBlockValue || value === `'${whiteBlockValue}'`),
);

if (whiteBlockActiveAttr) {
removeAttrs(
[whiteBlockActiveAttr],
sourceCodeLocation as ElementLocation,
recorder,
templateOffset,
);

const {startOffset} = sourceCodeLocation?.attrs?.[
whiteBlockActiveAttr.name
] || {startOffset: 0, endOffset: 0};

recorder.insertLeft(
startOffset + templateOffset,
` ${appearanceInputName}="whiteblock"`,
);
recorder.insertLeft(startOffset + templateOffset, ' data-mode="checked"');
}
});

const elementWithConditionAppearance = elements.find(({attrs}: Element) =>
attrs.some(
({name, value}) =>
name === `[${appearanceInputName}]` && !value.trim().startsWith("'"),
),
);

if (elementWithConditionAppearance) {
addTodo(recorder, templateOffset);
}
}

function addTodo(recorder: UpdateRecorder, templateOffset: number): void {
recorder.insertRight(
templateOffset,
'<!-- Taiga migration TODO: tuiButton "whiteblock-active" appearance is no longer available. Use \'appearance="whiteblock" data-mode="checked"\' -->\n',
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import {join} from 'node:path';

import {HostTree} from '@angular-devkit/schematics';
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
import type {TuiSchema} from '@taiga-ui/cdk/schematics/ng-add/schema';
import {
createProject,
createSourceFile,
resetActiveProject,
saveActiveProject,
setActiveProject,
} from 'ng-morph';

import {createAngularJson} from '../../../utils/create-angular-json';

const collectionPath = join(__dirname, '../../../migration.json');

const COMPONENT = `
import {TuiButton} from '@taiga-ui/core/components/button';
@Component({
standalone: true,
templateUrl: './test.template.html',
imports: [TuiButton]
})
class TestComponent {}
`;

const COMPONENT_WITH_CONDITION = `
import {TuiButton} from '@taiga-ui/core/components/button';
@Component({
standalone: true,
templateUrl: './test-with-condition.template.html',
imports: [TuiButton]
})
class TestWithConditionComponent {
get appearance() {return 'flat'};
}
`;

const TEMPLATE_BEFORE = `
<button tuiButton></button>
<button tuiButton [appearance]="'whiteblock-active'"></button>
<button tuiButton appearance="whiteblock-active"></button>
<a tuiIconButton [appearance]="'whiteblock-active'"></a>
<a tuiButton [appearance]="
'flat'
"></a>
`;

const TEMPLATE_WITH_CONDITION_BEFORE = `
<a tuiButton [appearance]="
true ? appearance : 'flat'
"></a>
`;

const TEMPLATE_AFTER = `
<button tuiButton></button>
<button tuiButton appearance="whiteblock" data-mode="checked"></button>
<button tuiButton appearance="whiteblock" data-mode="checked"></button>
<a tuiIconButton appearance="whiteblock" data-mode="checked"></a>
<a tuiButton [appearance]="
'flat'
"></a>
`;

const TEMPLATE_WITH_CONDITION_AFTER = `<!-- Taiga migration TODO: tuiButton "whiteblock-active" appearance is no longer available. Use 'appearance="whiteblock" data-mode="checked"' -->
<a tuiButton [appearance]="
true ? appearance : 'flat'
"></a>
`;

describe('ng-update', () => {
let host: UnitTestTree;
let runner: SchematicTestRunner;

beforeEach(() => {
host = new UnitTestTree(new HostTree());
runner = new SchematicTestRunner('schematics', collectionPath);

setActiveProject(createProject(host));

createMainFiles();

saveActiveProject();
});

it('should migrate button appearance in a template', async () => {
const tree = await runner.runSchematic(
'updateToV4',
{'skip-logs': process.env['TUI_CI'] === 'true'} as Partial<TuiSchema>,
host,
);

expect(tree.readContent('test/app/test.template.html')).toEqual(TEMPLATE_AFTER);
});

it('should migrate button appearance with condition in a template', async () => {
const tree = await runner.runSchematic(
'updateToV4',
{'skip-logs': process.env['TUI_CI'] === 'true'} as Partial<TuiSchema>,
host,
);

expect(tree.readContent('test/app/test-with-condition.template.html')).toEqual(
TEMPLATE_WITH_CONDITION_AFTER,
);
});

afterEach(() => {
resetActiveProject();
});
});

function createMainFiles(): void {
createSourceFile('test/app/test.component.ts', COMPONENT);
createSourceFile('test/app/test.template.html', TEMPLATE_BEFORE);

createSourceFile(
'test/app/test-with-condition.component.ts',
COMPONENT_WITH_CONDITION,
);
createSourceFile(
'test/app/test-with-condition.template.html',
TEMPLATE_WITH_CONDITION_BEFORE,
);

createAngularJson();
createSourceFile(
'package.json',
'{"dependencies": {"@angular/core": "~13.0.0", "@taiga-ui/addon-commerce": "~3.42.0"}}',
);
}

0 comments on commit 9c583df

Please sign in to comment.