Skip to content

Commit

Permalink
chore(uve): FTM - Implement "Bookmarks" button on UVE Toolbar (#30793)
Browse files Browse the repository at this point in the history
### Showcase


https://github.com/user-attachments/assets/b25924df-46ed-465c-895e-89623863739e

This pull request introduces significant changes to the
`core-web/libs/portlets/edit-ema` module, focusing on the new toolbar
variant and its integration with the existing components. The key
changes include the addition of the `TOOLBAR_VARIANTS` enum, updates to
the `dot-ema-bookmarks` and `dot-uve-toolbar` components, and
modifications to the state management for the new toolbar variant.

### Toolbar Variants and Component Updates:

* **Introduction of `TOOLBAR_VARIANTS` Enum:**
- Added `TOOLBAR_VARIANTS` enum to define different toolbar styles,
including the new UVE toolbar variant.

* **Component Updates for Toolbar Variants:**
- Updated `dot-ema-bookmarks.component.html` to conditionally render
buttons based on the toolbar variant.
- Modified `dot-uve-toolbar.component.html` to include conditional
rendering for the new toolbar variant and preview mode.
[[1]](diffhunk://#diff-9937556e73b051b878ba22ad1ce971a70019a617d7979b3e0bcc814801ad350bL3-R12)
[[2]](diffhunk://#diff-9937556e73b051b878ba22ad1ce971a70019a617d7979b3e0bcc814801ad350bR23-R26)
[[3]](diffhunk://#diff-9937556e73b051b878ba22ad1ce971a70019a617d7979b3e0bcc814801ad350bR36-R39)
- Enhanced `dot-uve-toolbar.component.ts` to integrate the
`TOOLBAR_VARIANTS` enum and handle preview mode toggling.

### State Management Enhancements:

* **State Management for Toolbar Variants:**
- Introduced `withUVEToolbar` to manage the state of the new toolbar
variant, including methods for toggling preview mode.
- Updated the `withEditor` function to include the `withUVEToolbar`
state feature.
[[1]](diffhunk://#diff-86e692578757ed7f4f6cba5d0aeb07641312f3b17885825d1a45987153ae87f0R22-R24)
[[2]](diffhunk://#diff-86e692578757ed7f4f6cba5d0aeb07641312f3b17885825d1a45987153ae87f0R70)

### Testing Enhancements:

* **Unit Tests for New Toolbar Variant:**
- Updated `dot-uve-toolbar.component.spec.ts` to include tests for the
new toolbar variant and its attributes.
[[1]](diffhunk://#diff-3eaa147616a5d1ff374a5fa27b0f38f0159a9039ef7e8d672dec43631f48a9e1L1-R130)
[[2]](diffhunk://#diff-3eaa147616a5d1ff374a5fa27b0f38f0159a9039ef7e8d672dec43631f48a9e1R157)
- Added tests in `withEditor.spec.ts` to validate the state management
of the new toolbar variant.

These changes collectively enhance the flexibility and functionality of
the toolbar in the UVE interface, providing a more dynamic user
experience.

---------

Co-authored-by: Kevin Davila <[email protected]>
  • Loading branch information
KevinDavilaDotCMS and kevindaviladev authored Nov 30, 2024
1 parent 3dd6041 commit 910d7e2
Show file tree
Hide file tree
Showing 11 changed files with 412 additions and 50 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
<p-button
(onClick)="toggleBookmark()"
[loading]="loading()"
[icon]="bookmarked() ? 'pi pi-star-fill' : 'pi pi-star'"
[label]="'editpage.toolbar.bookmark' | dm"
styleClass="p-button-text p-button-sm"
data-testId="bookmark-button" />
@if (store.$previewMode()) {
<p-button
(click)="toggleBookmark()"
[icon]="bookmarked() ? 'pi pi-star-fill' : 'pi pi-star'"
[loading]="loading()"
styleClass="p-button-text"
data-testId="bookmark-button" />
} @else {
<p-button
(onClick)="toggleBookmark()"
[loading]="loading()"
[icon]="bookmarked() ? 'pi pi-star-fill' : 'pi pi-star'"
[label]="'editpage.toolbar.bookmark' | dm"
styleClass="p-button-text p-button-sm"
data-testId="bookmark-button" />
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { describe, expect, it } from '@jest/globals';
import { Spectator, createComponentFactory } from '@ngneat/spectator/jest';
import { Spectator, createComponentFactory, mockProvider } from '@ngneat/spectator';
import { of } from 'rxjs';

import { AsyncPipe } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { signal } from '@angular/core';
import { By } from '@angular/platform-browser';

import { ButtonModule } from 'primeng/button';
Expand All @@ -17,6 +18,8 @@ import { LoginServiceMock, MockDotMessageService } from '@dotcms/utils-testing';

import { DotEmaBookmarksComponent } from './dot-ema-bookmarks.component';

import { UVEStore } from '../../../store/dot-uve.store';

describe('DotEmaBookmarksComponent', () => {
let spectator: Spectator<DotEmaBookmarksComponent>;

Expand All @@ -26,6 +29,13 @@ describe('DotEmaBookmarksComponent', () => {
providers: [
DialogService,
HttpClient,
// {
// provide: UVEStore,
// useValue: {
// $previewMode: signal(false)
// }
// },
mockProvider(UVEStore, { $previewMode: signal(false) }),
{
provide: LoginService,
useClass: LoginServiceMock
Expand Down Expand Up @@ -116,4 +126,16 @@ describe('DotEmaBookmarksComponent', () => {
})
);
});

describe('preview mode', () => {
it('should render the bookmark button with new UVE toolbar style when preview mode is true', () => {
const store = spectator.inject(UVEStore, true);
jest.spyOn(store, '$previewMode').mockReturnValue(signal(true));

spectator.detectChanges();
const button = spectator.debugElement.query(By.css('[data-testId="bookmark-button"]'));

expect(button.componentInstance.textContent).toBe(undefined);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { DotCMSContentlet } from '@dotcms/dotcms-models';
import { DotFavoritePageComponent } from '@dotcms/portlets/dot-ema/ui';
import { DotMessagePipe } from '@dotcms/ui';

import { UVEStore } from '../../../store/dot-uve.store';

@Component({
selector: 'dot-ema-bookmarks',
standalone: true,
Expand All @@ -25,6 +27,7 @@ export class DotEmaBookmarksComponent implements OnInit {
private readonly dotFavoritePageService = inject(DotFavoritePageService);
private readonly dialogService = inject(DialogService);
private readonly dotMessageService = inject(DotMessageService);
protected readonly store = inject(UVEStore);

favoritePage: DotCMSContentlet;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
<p-toolbar>
<div class="p-toolbar-group-start">
<p-button icon="pi pi-eye" styleClass="p-button-text" data-testId="uve-toolbar-preview" />
@if ($toolbar().editor) {
<p-button
icon="pi pi-eye"
styleClass="p-button-text"
data-testId="uve-toolbar-preview"
(click)="togglePreviewMode(true)" />

<p-button icon="pi pi-star" styleClass="p-button-text" data-testId="uve-toolbar-bookmark" />
<dot-ema-bookmarks [url]="$toolbar().editor.bookmarksUrl" />

<p-button
icon="pi pi-external-link"
styleClass="p-button-text"
data-testId="uve-toolbar-copy-url" />
<p-button
icon="pi pi-external-link"
styleClass="p-button-text"
data-testId="uve-toolbar-copy-url" />

<p-button
label="API"
styleClass="p-button-text p-button-sm"
data-testId="uve-toolbar-api-link" />
<p-button
label="API"
styleClass="p-button-text p-button-sm"
data-testId="uve-toolbar-api-link" />
} @else if ($toolbar().preview) {
<div>PREVIEW MODE CONTENT</div>
<button (click)="togglePreviewMode(false)">Back</button>
}
</div>

<div class="p-toolbar-group-end">
Expand All @@ -22,3 +31,7 @@
<span data-testId="uve-toolbar-workflow-actions">Workflows</span>
</div>
</p-toolbar>

@if ($toolbar().showInfoDisplay) {
<dot-ema-info-display data-testId="info-display" />
}
Original file line number Diff line number Diff line change
@@ -1,47 +1,157 @@
import { byTestId, Spectator } from '@ngneat/spectator';
import { byTestId, mockProvider, Spectator } from '@ngneat/spectator';
import { createComponentFactory } from '@ngneat/spectator/jest';
import { MockComponent } from 'ng-mocks';
import { of } from 'rxjs';

import { HttpClientTestingModule, provideHttpClientTesting } from '@angular/common/http/testing';
import { signal } from '@angular/core';

import { DotExperimentsService, DotLanguagesService, DotLicenseService } from '@dotcms/data-access';
import { LoginService } from '@dotcms/dotcms-js';
import {
DotExperimentsServiceMock,
DotLanguagesServiceMock,
DotLicenseServiceMock
} from '@dotcms/utils-testing';

import { DotUveToolbarComponent } from './dot-uve-toolbar.component';

import { DotPageApiService } from '../../../services/dot-page-api.service';
import { DEFAULT_PERSONA } from '../../../shared/consts';
import {
HEADLESS_BASE_QUERY_PARAMS,
MOCK_RESPONSE_HEADLESS,
MOCK_RESPONSE_VTL
} from '../../../shared/mocks';
import { UVEStore } from '../../../store/dot-uve.store';
import {
createFavoritePagesURL,
createFullURL,
createPageApiUrlWithQueryParams,
sanitizeURL
} from '../../../utils';
import { DotEmaBookmarksComponent } from '../dot-ema-bookmarks/dot-ema-bookmarks.component';

describe('DotUveToolbarComponent', () => {
let spectator: Spectator<DotUveToolbarComponent>;
const createComponent = createComponentFactory({
component: DotUveToolbarComponent
component: DotUveToolbarComponent,
imports: [HttpClientTestingModule, MockComponent(DotEmaBookmarksComponent)],
providers: [
UVEStore,
provideHttpClientTesting(),
{
provide: DotLanguagesService,
useValue: new DotLanguagesServiceMock()
},
{
provide: DotExperimentsService,
useValue: DotExperimentsServiceMock
},
{
provide: DotLicenseService,
useValue: new DotLicenseServiceMock()
},
{
provide: DotPageApiService,
useValue: {
get: () => of(MOCK_RESPONSE_HEADLESS)
}
},
{
provide: LoginService,
useValue: {
getCurrentUser: () => of({})
}
}
]
});

beforeEach(() => {
spectator = createComponent();
});
const params = HEADLESS_BASE_QUERY_PARAMS;
const url = sanitizeURL(params?.url);

it('should have preview button', () => {
expect(spectator.query(byTestId('uve-toolbar-preview'))).toBeTruthy();
});
const pageAPIQueryParams = createPageApiUrlWithQueryParams(url, params);
const pageAPIResponse = MOCK_RESPONSE_HEADLESS;

it('should have bookmark button', () => {
expect(spectator.query(byTestId('uve-toolbar-bookmark'))).toBeTruthy();
});
const pageAPI = `/api/v1/page/${'json'}/${pageAPIQueryParams}`;

it('should have copy url button', () => {
expect(spectator.query(byTestId('uve-toolbar-copy-url'))).toBeTruthy();
});
const shouldShowInfoDisplay = false || pageAPIResponse?.page.locked || false || false;

it('should have api link button', () => {
expect(spectator.query(byTestId('uve-toolbar-api-link'))).toBeTruthy();
const bookmarksUrl = createFavoritePagesURL({
languageId: Number(params?.language_id),
pageURI: url,
siteId: pageAPIResponse?.site.identifier
});

it('should have experiments button', () => {
expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeTruthy();
});
describe('base state', () => {
beforeEach(() => {
spectator = createComponent({
providers: [
mockProvider(UVEStore, {
$uveToolbar: signal({
editor: {
bookmarksUrl,
copyUrl: createFullURL(params, pageAPIResponse?.site.identifier),
apiUrl: `${'http://localhost'}${pageAPI}`
},
preview: null,

it('should have language selector', () => {
expect(spectator.query(byTestId('uve-toolbar-language-selector'))).toBeTruthy();
});
currentLanguage: pageAPIResponse?.viewAs.language,
urlContentMap: null,
runningExperiment: null,
workflowActionsInode: pageAPIResponse?.page.inode,
unlockButton: null,
showInfoDisplay: shouldShowInfoDisplay,
personaSelector: {
pageId: pageAPIResponse?.page.identifier,
value: pageAPIResponse?.viewAs.persona ?? DEFAULT_PERSONA
}
}),
setDevice: jest.fn(),
setSocialMedia: jest.fn(),
pageParams: signal(params),
pageAPIResponse: signal(MOCK_RESPONSE_VTL),
reloadCurrentPage: jest.fn(),
loadPageAsset: jest.fn()
})
]
});
});

it('should have persona selector', () => {
expect(spectator.query(byTestId('uve-toolbar-persona-selector'))).toBeTruthy();
});
describe('dot-ema-bookmarks', () => {
it('should have attr', () => {
const bookmarks = spectator.query(DotEmaBookmarksComponent);

expect(bookmarks.url).toBe('/test-url?host_id=123-xyz-567-xxl&language_id=1');
});
});

it('should have preview button', () => {
expect(spectator.query(byTestId('uve-toolbar-preview'))).toBeTruthy();
});

it('should have copy url button', () => {
expect(spectator.query(byTestId('uve-toolbar-copy-url'))).toBeTruthy();
});

it('should have api link button', () => {
expect(spectator.query(byTestId('uve-toolbar-api-link'))).toBeTruthy();
});

it('should have experiments button', () => {
expect(spectator.query(byTestId('uve-toolbar-running-experiment'))).toBeTruthy();
});

it('should have language selector', () => {
expect(spectator.query(byTestId('uve-toolbar-language-selector'))).toBeTruthy();
});

it('should have persona selector', () => {
expect(spectator.query(byTestId('uve-toolbar-persona-selector'))).toBeTruthy();
});

it('should have workflows button', () => {
expect(spectator.query(byTestId('uve-toolbar-workflow-actions'))).toBeTruthy();
it('should have workflows button', () => {
expect(spectator.query(byTestId('uve-toolbar-workflow-actions'))).toBeTruthy();
});
});
});
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ChangeDetectionStrategy, Component, inject } from '@angular/core';

import { ButtonModule } from 'primeng/button';
import { ToolbarModule } from 'primeng/toolbar';

import { DotMessagePipe } from '@dotcms/ui';
import { UVEStore } from '../../../store/dot-uve.store';
import { DotEmaBookmarksComponent } from '../dot-ema-bookmarks/dot-ema-bookmarks.component';
import { DotEmaInfoDisplayComponent } from '../dot-ema-info-display/dot-ema-info-display.component';

@Component({
selector: 'dot-uve-toolbar',
standalone: true,
imports: [ButtonModule, DotMessagePipe, ToolbarModule],
imports: [ButtonModule, ToolbarModule, DotEmaBookmarksComponent, DotEmaInfoDisplayComponent],
templateUrl: './dot-uve-toolbar.component.html',
styleUrl: './dot-uve-toolbar.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DotUveToolbarComponent {}
export class DotUveToolbarComponent {
#store = inject(UVEStore);

readonly $toolbar = this.#store.$uveToolbar;

togglePreviewMode(preview: boolean) {
this.#store.togglePreviewMode(preview);
}
}
Loading

0 comments on commit 910d7e2

Please sign in to comment.