Skip to content

Commit

Permalink
feat(chrome-ext): toggle button for placeholder
Browse files Browse the repository at this point in the history
  • Loading branch information
matthieu-crouzet committed Apr 16, 2024
1 parent e030189 commit e16c26c
Show file tree
Hide file tree
Showing 57 changed files with 541 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
import { AsyncPipe, TitleCasePipe } from '@angular/common';
import { ChangeDetectionStrategy, Component, type OnDestroy, ViewEncapsulation } from '@angular/core';
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { Subscription } from 'rxjs';
import { ChromeExtensionConnectionService } from '../../services/connection.service';
import { DebugPanelService } from './debug-panel.service';
import { AsyncPipe } from '@angular/common';

type PlaceholderMode = 'normal' | 'debug' | 'pending';

@Component({
selector: 'o3r-debug-panel-pres',
Expand All @@ -10,14 +14,58 @@ import { AsyncPipe } from '@angular/common';
encapsulation: ViewEncapsulation.None,
standalone: true,
imports: [
AsyncPipe
ReactiveFormsModule,
FormsModule,
AsyncPipe,
TitleCasePipe
]
})
export class DebugPanelPresComponent {
export class DebugPanelPresComponent implements OnDestroy {
public readonly form = new FormGroup({
visualTesting: new FormControl<boolean>(false),
placeholderMode: new FormControl<PlaceholderMode>('normal')
});

public placeholderModes: PlaceholderMode[] = ['normal', 'debug', 'pending'];

/** Application information stream */
public applicationInformation$ = this.service.applicationInformation$;
public readonly applicationInformation$ = this.service.applicationInformation$;

private readonly subscription = new Subscription();

constructor(
private readonly service: DebugPanelService,
private readonly connection: ChromeExtensionConnectionService
) {
this.subscription.add(
this.form.controls.visualTesting.valueChanges.subscribe((value) => {
this.toggleVisualTestingRender(!!value);
})
);
this.subscription.add(
this.form.controls.placeholderMode.valueChanges.subscribe((value) => {
if (value) {
this.togglePlaceholderMode(value);
}
})
);
}

/**
* Toggle visual testing mode
* @param toggle
*/
private toggleVisualTestingRender(toggle: boolean) {
this.connection.sendMessage('toggleVisualTesting', { toggle });
}

constructor(private readonly service: DebugPanelService, private readonly connection: ChromeExtensionConnectionService) {}
/**
* Toggle placeholder mode
* @param mode
*/
private togglePlaceholderMode(mode: PlaceholderMode) {
this.connection.sendMessage('placeholderMode', { mode });
}

/** Refresh Application information */
public refreshInfo() {
Expand All @@ -26,13 +74,7 @@ export class DebugPanelPresComponent {
});
}

/**
* Toggle visual testing mode
* @param event
*/
public toggleVisualTestingRender(event: UIEvent) {
this.connection.sendMessage('toggleVisualTesting', {
toggle: (event.target as HTMLInputElement).checked
});
public ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,24 @@ <h4 class="d-inline-block">Information</h4>
}
<div>
<h4>Actions</h4>
<div>
<div class="form-check">
<input class="form-check-input" type="checkbox" (change)="toggleVisualTestingRender($event)" id="toggleVisualTesting">
<label class="form-check-label" for="toggleVisualTesting">
<form class="d-inline-flex flex-column gap-4" [formGroup]="form">
<div class="d-flex flex-column">
<label for="toggleVisualTesting">
Toggle visual testing
</label>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" formControlName="visualTesting" id="toggleVisualTesting">
<label class="form-check-label" for="toggleVisualTesting">{{ form.controls.visualTesting.value ? 'On' : 'Off' }}</label>
</div>
</div>
</div>
<div class="d-flex flex-column">
<div>Placeholder mode</div>
<div class="btn-group" role="group" aria-label="Basic checkbox toggle button group">
@for (mode of placeholderModes; track mode) {
<input type="radio" class="btn-check" [id]="mode" [value]="mode" autocomplete="off" formControlName="placeholderMode">
<label class="btn btn-outline-primary" [for]="mode">{{ mode | titlecase }}</label>
}
</div>
</div>
</form>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</div>
}
</div>
<div class="form-check d-flex flex-column gap-2">
<div class="form-check form-switch d-flex flex-column gap-2">
<div>
<input class="form-check-input" type="checkbox" formControlName="showKeys" id="displayLocalizationKey" [attr.aria-describedby]="isTranslationDeactivationEnabled() ? null : 'show-keys-hint'">
<label class="form-check-label text-nowrap" for="displayLocalizationKey">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<form [formGroup]="form">
@for (item of controlsType() | keyvalue; track item.key) {
<div class="mb-2" [ngClass]="item.value === 'boolean' ? 'form-check' : 'form-group'">
<div class="mb-2" [ngClass]="item.value === 'boolean' ? 'form-check form-switch' : 'form-group'">
@switch (item.value) {
@case ('boolean') {
<input class="form-check-input" [formControlName]="item.key" type="checkbox" [id]="item.key" />
Expand Down
10 changes: 10 additions & 0 deletions apps/showcase/e2e-playwright/sanity/lighthouse-sanity.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,14 @@ test.describe('Lighthouse tests', () => {
await performAudit('sdk-generator', page);
await page.close();
});

test('placeholder', async ({context}) => {
const page = await context.newPage();
await page.goto(baseUrl);
const appFixture = new AppFixtureComponent(new O3rElement({element: page.locator('app-root'), page}));
await appFixture.navigateToPlaceholder();
await page.waitForURL('**/placeholder');
await performAudit('placeholder', page);
await page.close();
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions apps/showcase/e2e-playwright/sanity/visual-sanity.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,11 @@ test.describe.serial('Sanity test', () => {
await waitForPetStore;
await expect(page).toHaveScreenshot([browserName, 'sdk-generator.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]});
});

await test.step('placeholder', async () => {
await appFixture.navigateToPlaceholder();
await page.waitForURL('**/placeholder');
await expect(page).toHaveScreenshot([browserName, 'placeholder.png'], {fullPage: true, mask: [page.locator('.visual-testing-ignore')]});
});
});
});
13 changes: 12 additions & 1 deletion apps/showcase/placeholders.metadata.manual.json
Original file line number Diff line number Diff line change
@@ -1 +1,12 @@
[]
[
{
"library": "@o3r/showcase",
"name": "PlaceholderComponent",
"placeholders": [
{
"id": "placeholder-showcase",
"description": "Showcase placeholder in app"
}
]
}
]
1 change: 1 addition & 0 deletions apps/showcase/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const appRoutes: Routes = [
{path: 'home', loadComponent: () => import('./home/index').then((m) => m.HomeComponent), title: 'Otter Showcase - Home'},
{path: 'run-app-locally', loadComponent: () => import('./run-app-locally/index').then((m) => m.RunAppLocallyComponent), title: 'Otter Showcase - Run App Locally'},
{path: 'sdk', loadComponent: () => import('./sdk/index').then((m) => m.SdkComponent), title: 'Otter Showcase - SDK'},
{path: 'placeholder', loadComponent: () => import('./placeholder/index').then((m) => m.PlaceholderComponent), title: 'Otter Showcase - Placeholder'},
{path: '**', redirectTo: '/home', pathMatch: 'full'}
];

Expand Down
7 changes: 4 additions & 3 deletions apps/showcase/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ export class AppComponent implements OnDestroy {
]
},
{
label: 'CMS',
label: 'Personalization',
links: [
{ url: '/configuration', label: 'Configuration' },
{ url: '/localization', label: 'Localization' },
{ url: '/design-token', label: 'Design Tokens' },
{ url: '/dynamic-content', label: 'Dynamic content' },
{ url: '/component-replacement', label: 'Component replacement' },
{ url: '/rules-engine', label: 'Rules engine' },
{ url: '/component-replacement', label: 'Component Replacement' },
{ url: '/design-token', label: 'Design Tokens' }
{ url: '/placeholder', label: 'Placeholder' }
]
},
{
Expand Down
15 changes: 11 additions & 4 deletions apps/showcase/src/app/app.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export interface AppFixture extends ComponentFixtureProfile {
navigateToDesignToken(): Promise<void>;
/** Go to SDK-generator page */
navigateToSDKGenerator(): Promise<void>;
/** Go to placeholder page */
navigateToPlaceholder(): Promise<void>;
}

export class AppFixtureComponent extends O3rComponentFixture implements AppFixture {
Expand Down Expand Up @@ -56,12 +58,12 @@ export class AppFixtureComponent extends O3rComponentFixture implements AppFixtu
}

/** @inheritDoc */
public async navigateToDynamicContent() {
public async navigateToDesignToken() {
await (await this.getSideNav()).clickOnLink(4);
}

/** @inheritDoc */
public async navigateToRulesEngine() {
public async navigateToDynamicContent() {
await (await this.getSideNav()).clickOnLink(5);
}

Expand All @@ -71,13 +73,18 @@ export class AppFixtureComponent extends O3rComponentFixture implements AppFixtu
}

/** @inheritDoc */
public async navigateToDesignToken() {
public async navigateToRulesEngine() {
await (await this.getSideNav()).clickOnLink(7);
}

/** @inheritDoc */
public async navigateToSDKGenerator() {
public async navigateToPlaceholder() {
await (await this.getSideNav()).clickOnLink(8);
}

/** @inheritDoc */
public async navigateToSDKGenerator() {
await (await this.getSideNav()).clickOnLink(9);
}

}
1 change: 1 addition & 0 deletions apps/showcase/src/app/placeholder/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './placeholder.component';
69 changes: 69 additions & 0 deletions apps/showcase/src/app/placeholder/placeholder.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { AsyncPipe } from '@angular/common';
import { type AfterViewInit, ChangeDetectionStrategy, Component, type QueryList, ViewChildren, ViewEncapsulation } from '@angular/core';
import { RouterModule } from '@angular/router';
import { O3rComponent } from '@o3r/core';
import { PlaceholderRulesEngineActionHandler, PlaceholderRulesEngineActionModule } from '@o3r/components/rules-engine';
import { DynamicContentModule, DynamicContentService } from '@o3r/dynamic-content';
import { RulesEngineDevtoolsModule, RulesEngineRunnerModule, RulesEngineRunnerService, RulesetsStore, setRulesetsEntities } from '@o3r/rules-engine';
import { Store } from '@ngrx/store';
import { firstValueFrom } from 'rxjs';
import { IN_PAGE_NAV_PRES_DIRECTIVES, type InPageNavLink, InPageNavLinkDirective, InPageNavPresService, PlaceholderPresComponent } from '../../components';
import { environment } from '../../environments/environment.development';
import { TripFactsService } from '../../facts';

@O3rComponent({ componentType: 'Page' })
@Component({
selector: 'o3r-placeholder-page',
standalone: true,
imports: [
PlaceholderPresComponent,
DynamicContentModule,
RulesEngineRunnerModule,
RulesEngineDevtoolsModule,
PlaceholderRulesEngineActionModule,
RouterModule,
IN_PAGE_NAV_PRES_DIRECTIVES,
AsyncPipe
],
templateUrl: './placeholder.template.html',
styleUrl: './placeholder.style.scss',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PlaceholderComponent implements AfterViewInit {
@ViewChildren(InPageNavLinkDirective)
private readonly inPageNavLinkDirectives!: QueryList<InPageNavLink>;
public links$ = this.inPageNavPresService.links$;

constructor(
private readonly inPageNavPresService: InPageNavPresService,
private readonly tripFactsService: TripFactsService,
private readonly store: Store<RulesetsStore>,
private readonly dynamicContentService: DynamicContentService,
rulesEngine: RulesEngineRunnerService,
placeholderRulesEngineActionHandler: PlaceholderRulesEngineActionHandler
) {
// We recommend to do the 3 next lines in the AppComponent
// Here we do it for the sake of the example
this.tripFactsService.register();
rulesEngine.actionHandlers.add(placeholderRulesEngineActionHandler);
void this.loadRuleSet();
}

private async loadRuleSet() {
const path = await firstValueFrom(
this.dynamicContentService.getContentPathStream(
`${!environment.production ? 'assets/' : ''}rules/rulesets.json`
)
);

const resultCall = await fetch(path);
const result = await resultCall.json();

this.store.dispatch(setRulesetsEntities({ entities: result.rulesets }));
}

public ngAfterViewInit() {
this.inPageNavPresService.initialize(this.inPageNavLinkDirectives);
}
}
37 changes: 37 additions & 0 deletions apps/showcase/src/app/placeholder/placeholder.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { RulesEngineRunnerModule } from '@o3r/rules-engine';
import { PlaceholderComponent } from './placeholder.component';

describe('PlaceholderComponent', () => {
let component: PlaceholderComponent;
let fixture: ComponentFixture<PlaceholderComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
PlaceholderComponent,
StoreModule.forRoot(),
EffectsModule.forRoot(),
RulesEngineRunnerModule.forRoot()
]
}).compileComponents();

global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({
rulesets: []
})
})
) as jest.Mock;

fixture = TestBed.createComponent(PlaceholderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
3 changes: 3 additions & 0 deletions apps/showcase/src/app/placeholder/placeholder.style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
o3r-placeholder-page {

}
29 changes: 29 additions & 0 deletions apps/showcase/src/app/placeholder/placeholder.template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<h1>Placeholder</h1>
<div class="row">
<div class="right-nav order-1 order-lg-2 col-12 col-lg-2 sticky-lg-top pt-5 pt-lg-0">
<o3r-in-page-nav-pres
id="localization-nav"
[links]="links$ | async"
>
</o3r-in-page-nav-pres>
</div>
<div class="order-2 order-lg-1 col-12 col-lg-10">
<h2 id="localization-description">Description</h2>
<div>
<p>The Otter framework provides a placeholder component mechanism to help integrate dynamic HTML elements (with a basic rendering system) at a predefined position in the application.</p>
</div>

<h2 id="localization-example">Example</h2>
<div class="d-flex flex-column">
<o3r-placeholder-pres></o3r-placeholder-pres>
</div>
<h2 id="localization-references">References</h2>
<div>
<ul>
<li>
<a href="https://github.com/AmadeusITGroup/otter/blob/main/docs/components/PLACEHOLDERS.md" target="_blank" rel="noopener">Placeholder documentation</a>
</li>
</ul>
</div>
</div>
</div>
Loading

0 comments on commit e16c26c

Please sign in to comment.