Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { dotcmsContentTypeFieldBasicMock, MockDotMessageService } from '@dotcms/
import { ContentTypeFieldsTabComponent } from '.';

import { DOTTestBed } from '../../../../../../test/dot-test-bed';
import { DotMaxlengthDirective } from '../../../../../../view/directives/dot-maxlength/dot-maxlength.directive';

const tabField: DotCMSContentTypeField = {
...dotcmsContentTypeFieldBasicMock,
Expand Down Expand Up @@ -59,7 +60,7 @@ describe('ContentTypeFieldsTabComponent', () => {
beforeEach(waitForAsync(() => {
DOTTestBed.configureTestingModule({
declarations: [ContentTypeFieldsTabComponent, DotTestHostComponent],
imports: [TooltipModule, ButtonModule, DotMessagePipe],
imports: [TooltipModule, ButtonModule, DotMessagePipe, DotMaxlengthDirective],
providers: [
DotAlertConfirmService,
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
<div class="h-full min-h-0 flex flex-col">
<p-tabs [value]="0" class="flex flex-col grow min-h-0">
<p-tabs
[value]="$activeTab()"
(valueChange)="onTabChange($event)"
class="flex flex-col grow min-h-0">
<p-tablist
class="shrink-0"
[pt]="{ root: { class: '!bg-surface-50 border-b border-gray-200' } }">
<p-tab [value]="0">
<p-tab value="fields">
{{ 'contenttypes.tab.fields.header' | dm }}
</p-tab>
@if ($contentType() && $showStyleEditorTab()) {
<p-tab [value]="1" data-testid="style-editor-tab">
<p-tab value="style-editor" data-testid="style-editor-tab">
{{ 'contenttypes.tab.style.editor.header' | dm }}
</p-tab>
}
@if ($contentType() && showPermissionsTab | async) {
<p-tab [value]="2">
@if ($contentType() && $showPermissionsTab()) {
<p-tab value="permissions">
{{ 'contenttypes.tab.permissions.header' | dm }}
</p-tab>
}
@if ($contentType()) {
<p-tab [value]="$contentType() && (showPermissionsTab | async) ? 3 : 2">
<p-tab value="push-history">
{{ 'contenttypes.tab.publisher.push.history.header' | dm }}
</p-tab>
}
Expand All @@ -42,7 +45,7 @@
</div>
</p-tablist>
<p-tabpanels class="grow basis-0 min-h-0 p-0!">
<p-tabpanel [value]="0" class="h-full min-h-0">
<p-tabpanel value="fields" class="h-full min-h-0">
<div class="flex h-full min-h-0" id="content-type-form-layout">
<div
class="flex flex-col grow min-h-0 overflow-x-hidden overflow-y-auto bg-gray-200 p-6"
Expand All @@ -61,12 +64,15 @@
</div>
</p-tabpanel>
@if ($contentType() && $showStyleEditorTab()) {
<p-tabpanel [value]="1" class="h-full min-h-0" data-testid="style-editor-panel">
<p-tabpanel
value="style-editor"
class="h-full min-h-0"
data-testid="style-editor-panel">
<dot-style-editor-builder [contentType]="$contentType()" />
</p-tabpanel>
}
@if ($contentType() && showPermissionsTab | async) {
<p-tabpanel [value]="2" class="h-full">
@if ($contentType() && $showPermissionsTab()) {
<p-tabpanel value="permissions" class="h-full">
<div class="h-full">
<dot-portlet-box class="h-full">
<dot-iframe class="h-full" [src]="permissionURL" />
Expand All @@ -75,9 +81,7 @@
</p-tabpanel>
}
@if ($contentType()) {
<p-tabpanel
[value]="$contentType() && (showPermissionsTab | async) ? 3 : 2"
class="h-full">
<p-tabpanel value="push-history" class="h-full">
<div class="h-full">
<dot-portlet-box class="h-full">
<dot-iframe class="h-full" [src]="pushHistoryURL" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ Object.defineProperty(window, 'matchMedia', {
}))
});

import { BehaviorSubject, of } from 'rxjs';
import { EMPTY, of } from 'rxjs';

import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { ActivatedRoute, Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';

import { MenuItem } from 'primeng/api';
Expand All @@ -35,12 +36,11 @@ import {
DotHttpErrorManagerService,
DotIframeService,
DotMessageService,
DotPropertiesService,
DotRouterService,
DotUiColorsService
} from '@dotcms/data-access';
import { DotcmsEventsService, LoggerService, LoginService } from '@dotcms/dotcms-js';
import { DotCMSContentType } from '@dotcms/dotcms-models';
import { DotCMSContentType, FeaturedFlags } from '@dotcms/dotcms-models';
import {
DotApiLinkComponent,
DotCopyButtonComponent,
Expand Down Expand Up @@ -136,12 +136,8 @@ const fakeContentType: DotCMSContentType = {
describe('ContentTypesLayoutComponent', () => {
let fixture: ComponentFixture<TestHostComponent>;
let de: DebugElement;
let featureFlagSubject: BehaviorSubject<boolean>;

beforeEach(() => {
// Default: feature enabled. Tests that need it disabled can emit false.
featureFlagSubject = new BehaviorSubject<boolean>(true);

const messageServiceMock = new MockDotMessageService({
'contenttypes.sidebar.components.title': 'Field Title',
'contenttypes.tab.fields.header': 'Fields Header Tab',
Expand Down Expand Up @@ -234,9 +230,18 @@ describe('ContentTypesLayoutComponent', () => {
useValue: { confirm: jest.fn(), alert: jest.fn() }
},
{
provide: DotPropertiesService,
provide: ActivatedRoute,
useValue: {
getFeatureFlag: jest.fn().mockReturnValue(featureFlagSubject.asObservable())
snapshot: {
data: {
featuredFlags: {
[FeaturedFlags.FEATURE_FLAG_UVE_STYLE_EDITOR]: true
},
tabPermissions: { showPermissionsTab: true }
}
},
firstChild: null,
events: EMPTY
}
}
]
Expand Down Expand Up @@ -274,7 +279,7 @@ describe('ContentTypesLayoutComponent', () => {
});

it('should not have a Permissions tab', () => {
const pTabPanel = de.query(By.css('p-tabpanel[value="2"]'));
const pTabPanel = de.query(By.css('p-tabpanel[value="permissions"]'));
expect(pTabPanel).toBeFalsy();
});

Expand All @@ -287,21 +292,21 @@ describe('ContentTypesLayoutComponent', () => {
expect(fieldDragDropService.setBagOptions).toHaveBeenCalledTimes(1);
});

it('should have dot-portlet-box in the Permissions tab after it has been clicked', fakeAsync(() => {
it('should navigate to the route and immediately update $activeTab when clicking a tab', () => {
fixture.componentRef.setInput('contentType', fakeContentType);
fixture.detectChanges();

const tabs = de.queryAll(By.css('p-tab'));
// tabs[0]=Fields, [1]=StyleEditor, [2]=Permissions
tabs[2].nativeElement.click();
fixture.detectChanges();
const router = fixture.debugElement.injector.get(Router);
jest.spyOn(router, 'navigate');

fixture.whenStable().then(() => {
const panels = de.queryAll(By.css('p-tabpanel'));
const contentTypePushHistoryPortletBox = panels[2].query(By.css('dot-portlet-box'));
expect(contentTypePushHistoryPortletBox).not.toBeNull();
});
}));
de.componentInstance.onTabChange('permissions');

expect(de.componentInstance.$activeTab()).toBe('permissions');
expect(router.navigate).toHaveBeenCalledWith(
['permissions'],
expect.objectContaining({ relativeTo: expect.anything() })
);
});

describe('Edit toolBar', () => {
beforeEach(() => {
Expand Down Expand Up @@ -335,13 +340,9 @@ describe('ContentTypesLayoutComponent', () => {

describe('Tabs', () => {
let iframe: DebugElement;
let dotCurrentUserService: DotCurrentUserService;

beforeEach(() => {
fixture.componentRef.setInput('contentType', fakeContentType);
dotCurrentUserService = fixture.debugElement.injector.get(DotCurrentUserService);
jest.spyOn(dotCurrentUserService, 'hasAccessToPortlet').mockReturnValue(of(true));

fixture.detectChanges();
});

Expand Down Expand Up @@ -498,7 +499,7 @@ describe('ContentTypesLayoutComponent', () => {
});

it('should hide the style editor tab when feature flag is disabled', () => {
featureFlagSubject.next(false);
de.componentInstance.$showStyleEditorTab.set(false);
fixture.detectChanges();

const styleEditorPanel = de.query(By.css('[data-testid="style-editor-panel"]'));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { Observable } from 'rxjs';

import { AsyncPipe } from '@angular/common';
import {
Component,
ElementRef,
Expand All @@ -10,9 +7,11 @@ import {
inject,
input,
output,
signal,
viewChild
} from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';

import { MenuItem } from 'primeng/api';
import { ButtonModule } from 'primeng/button';
Expand All @@ -21,12 +20,9 @@ import { MenuModule } from 'primeng/menu';
import { SplitButtonModule } from 'primeng/splitbutton';
import { TabsModule } from 'primeng/tabs';

import {
DotCurrentUserService,
DotEventsService,
DotMessageService,
DotPropertiesService
} from '@dotcms/data-access';
import { filter, map } from 'rxjs/operators';

import { DotEventsService, DotMessageService } from '@dotcms/data-access';
import { DotCMSContentType, FeaturedFlags } from '@dotcms/dotcms-models';
import { DotClipboardUtil, DotMessagePipe } from '@dotcms/ui';

Expand All @@ -43,7 +39,6 @@ import { DotStyleEditorBuilderComponent } from '../style-editor/dot-style-editor
templateUrl: 'content-types-layout.component.html',
providers: [DotClipboardUtil],
imports: [
AsyncPipe,
TabsModule,
SplitButtonModule,
ButtonModule,
Expand All @@ -61,9 +56,9 @@ export class ContentTypesLayoutComponent implements OnInit {
#dotMessageService = inject(DotMessageService);
#fieldDragDropService = inject(FieldDragDropService);
#dotEventsService = inject(DotEventsService);
#dotCurrentUserService = inject(DotCurrentUserService);
#dotPropertiesService = inject(DotPropertiesService);
#dotClipboardUtil = inject(DotClipboardUtil);
#router = inject(Router);
#route = inject(ActivatedRoute);

$contentType = input.required<DotCMSContentType>({ alias: 'contentType' });
openEditDialog = output<unknown>();
Expand All @@ -74,11 +69,14 @@ export class ContentTypesLayoutComponent implements OnInit {
permissionURL: string;
pushHistoryURL: string;
contentTypeNameInputSize: number;
showPermissionsTab: Observable<boolean>;
readonly $showStyleEditorTab = toSignal(
this.#dotPropertiesService.getFeatureFlag(FeaturedFlags.FEATURE_FLAG_UVE_STYLE_EDITOR),
{ initialValue: false }
readonly $showStyleEditorTab = signal<boolean>(
this.#route.snapshot.data['featuredFlags']?.[FeaturedFlags.FEATURE_FLAG_UVE_STYLE_EDITOR] ??
false
);
readonly $showPermissionsTab = signal<boolean>(
this.#route.snapshot.data['tabPermissions']?.showPermissionsTab ?? false
);
readonly $activeTab = signal(this.#route.firstChild?.snapshot.url[0]?.path ?? 'fields');
addToMenuContentType = false;

actions: MenuItem[];
Expand Down Expand Up @@ -115,7 +113,6 @@ export class ContentTypesLayoutComponent implements OnInit {
});

ngOnInit(): void {
this.showPermissionsTab = this.#dotCurrentUserService.hasAccessToPortlet('permissions');
this.#fieldDragDropService.setBagOptions();
this.loadActions();
}
Expand All @@ -128,6 +125,15 @@ export class ContentTypesLayoutComponent implements OnInit {
this.pushHistoryURL = `/html/content_types/push_history.jsp?contentTypeId=${ct.id}&popup=true`;
}
});

// Keep $activeTab in sync with browser back/forward navigation.
this.#router.events
.pipe(
filter((e) => e instanceof NavigationEnd),
map(() => this.#route.firstChild?.snapshot.url[0]?.path ?? 'fields'),
takeUntilDestroyed()
)
.subscribe((tab) => this.$activeTab.set(tab));
}

/**
Expand Down Expand Up @@ -178,6 +184,11 @@ export class ContentTypesLayoutComponent implements OnInit {
}
}

onTabChange(tab: unknown): void {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we add a string typing to this?

this.$activeTab.set(tab as string);
this.#router.navigate([tab as string], { relativeTo: this.#route });
}

private loadActions(): void {
this.actions = [
{
Expand Down
Loading
Loading