Skip to content

Commit 1cf680d

Browse files
authored
Add table-cell-link.component.ts (#15)
Signed-off-by: Pavel Shalamkov <[email protected]>
1 parent 9fbe23a commit 1cf680d

File tree

5 files changed

+211
-1
lines changed

5 files changed

+211
-1
lines changed

libs/schematic/generators/utils/modules.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const tableModules = (options: Schema) => [
8484
{name: 'DragDropModule', fromLib: '@angular/cdk/drag-drop'},
8585
{name: 'NgTemplateOutlet', fromLib: '@angular/common'},
8686
{name: 'DatePipe', fromLib: '@angular/common'},
87-
{name: 'EsmfTableCellLinkComponent', fromLib: '../../table-cell-link/esmf-table-cell-link.component'},
87+
{name: 'TableCellLinkComponent', fromLib: '../../src/lib/components/table-cell-link/table-cell-link.component'},
8888
{
8989
name: 'MatCheckboxModule',
9090
fromLib: '@angular/material/checkbox',
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<button
2+
[matTooltipDisabled]="!isDisabled()"
3+
[class.button-disabled]="isDisabled()"
4+
[matTooltip]="tooltipMessage()"
5+
(click)="openExternalLink()"
6+
matTooltipPosition="above"
7+
mat-icon-button
8+
aria-label="link row"
9+
>
10+
<mat-icon class="material-icons">open_in_new</mat-icon>
11+
</button>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
button {
2+
background-color: transparent;
3+
border: none;
4+
color: inherit;
5+
padding: 0;
6+
font: inherit;
7+
cursor: pointer;
8+
outline: inherit;
9+
}
10+
11+
.button-disabled {
12+
cursor: not-allowed;
13+
opacity: 0.4;
14+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import {ComponentFixture, TestBed} from '@angular/core/testing';
2+
import {TableCellLinkComponent} from './table-cell-link.component';
3+
import {By} from '@angular/platform-browser';
4+
import {ComponentRef} from '@angular/core';
5+
6+
describe('TableCellLinkComponent', () => {
7+
let component: TableCellLinkComponent;
8+
let fixture: ComponentFixture<TableCellLinkComponent>;
9+
let componentRef: ComponentRef<TableCellLinkComponent>;
10+
let windowOpenSpy: jest.SpyInstance;
11+
12+
const DISABLED_VALUE = '-';
13+
const VALID_URL = 'https://example.com';
14+
const ALTERNATIVE_URL = 'https://newsite.com';
15+
const TOOLTIP_MESSAGE = 'Open link';
16+
const NO_LINK_MESSAGE = 'No link';
17+
18+
beforeEach(async () => {
19+
await TestBed.configureTestingModule({
20+
imports: [TableCellLinkComponent],
21+
}).compileComponents();
22+
23+
fixture = TestBed.createComponent(TableCellLinkComponent);
24+
component = fixture.componentInstance;
25+
componentRef = fixture.componentRef;
26+
windowOpenSpy = jest.spyOn(window, 'open').mockImplementation();
27+
});
28+
29+
afterEach(() => {
30+
windowOpenSpy.mockRestore();
31+
});
32+
33+
describe('isDisabled computed signal', () => {
34+
it('should return true only when value is "-"', () => {
35+
componentRef.setInput('value', DISABLED_VALUE);
36+
componentRef.setInput('tooltipMessage', NO_LINK_MESSAGE);
37+
fixture.detectChanges();
38+
39+
expect(component.isDisabled()).toBe(true);
40+
});
41+
42+
it('should return false for any other value', () => {
43+
const testValues = [VALID_URL, '', 'some-text'];
44+
45+
testValues.forEach(value => {
46+
componentRef.setInput('value', value);
47+
componentRef.setInput('tooltipMessage', TOOLTIP_MESSAGE);
48+
fixture.detectChanges();
49+
50+
expect(component.isDisabled()).toBe(false);
51+
});
52+
});
53+
});
54+
55+
describe('openExternalLink', () => {
56+
it('should open link in new tab when enabled', () => {
57+
componentRef.setInput('value', VALID_URL);
58+
componentRef.setInput('tooltipMessage', TOOLTIP_MESSAGE);
59+
fixture.detectChanges();
60+
61+
component.openExternalLink();
62+
63+
expect(windowOpenSpy).toHaveBeenCalledWith(VALID_URL, '_blank');
64+
});
65+
66+
it('should not open link when disabled', () => {
67+
componentRef.setInput('value', DISABLED_VALUE);
68+
componentRef.setInput('tooltipMessage', NO_LINK_MESSAGE);
69+
fixture.detectChanges();
70+
71+
component.openExternalLink();
72+
73+
expect(windowOpenSpy).not.toHaveBeenCalled();
74+
});
75+
});
76+
77+
describe('UI rendering', () => {
78+
beforeEach(() => {
79+
componentRef.setInput('value', VALID_URL);
80+
componentRef.setInput('tooltipMessage', TOOLTIP_MESSAGE);
81+
fixture.detectChanges();
82+
});
83+
84+
it('should render button with icon', () => {
85+
const button = fixture.debugElement.query(By.css('button[mat-icon-button]'));
86+
const icon = fixture.debugElement.query(By.css('mat-icon'));
87+
88+
expect(button).toBeTruthy();
89+
expect(button.nativeElement.getAttribute('aria-label')).toBe('link row');
90+
expect(icon.nativeElement.textContent.trim()).toBe('open_in_new');
91+
});
92+
93+
it('should apply disabled class based on isDisabled state', () => {
94+
const button = fixture.debugElement.query(By.css('button'));
95+
expect(button.nativeElement.classList.contains('button-disabled')).toBe(false);
96+
97+
componentRef.setInput('value', DISABLED_VALUE);
98+
fixture.detectChanges();
99+
100+
expect(button.nativeElement.classList.contains('button-disabled')).toBe(true);
101+
});
102+
});
103+
104+
describe('UI interactions', () => {
105+
it('should open window when button is clicked and enabled', () => {
106+
componentRef.setInput('value', VALID_URL);
107+
componentRef.setInput('tooltipMessage', TOOLTIP_MESSAGE);
108+
fixture.detectChanges();
109+
110+
const button = fixture.debugElement.query(By.css('button'));
111+
button.nativeElement.click();
112+
113+
expect(windowOpenSpy).toHaveBeenCalledWith(VALID_URL, '_blank');
114+
});
115+
116+
it('should not open window when button is clicked and disabled', () => {
117+
componentRef.setInput('value', DISABLED_VALUE);
118+
componentRef.setInput('tooltipMessage', NO_LINK_MESSAGE);
119+
fixture.detectChanges();
120+
121+
const button = fixture.debugElement.query(By.css('button'));
122+
button.nativeElement.click();
123+
124+
expect(windowOpenSpy).not.toHaveBeenCalled();
125+
});
126+
});
127+
128+
describe('Dynamic input changes', () => {
129+
it('should update disabled state when value changes', () => {
130+
componentRef.setInput('value', VALID_URL);
131+
componentRef.setInput('tooltipMessage', TOOLTIP_MESSAGE);
132+
fixture.detectChanges();
133+
134+
expect(component.isDisabled()).toBe(false);
135+
136+
componentRef.setInput('value', DISABLED_VALUE);
137+
fixture.detectChanges();
138+
139+
expect(component.isDisabled()).toBe(true);
140+
});
141+
142+
it('should open new URL when value changes', () => {
143+
componentRef.setInput('value', VALID_URL);
144+
componentRef.setInput('tooltipMessage', TOOLTIP_MESSAGE);
145+
fixture.detectChanges();
146+
147+
const button = fixture.debugElement.query(By.css('button'));
148+
button.nativeElement.click();
149+
150+
expect(windowOpenSpy).toHaveBeenCalledWith(VALID_URL, '_blank');
151+
152+
componentRef.setInput('value', ALTERNATIVE_URL);
153+
fixture.detectChanges();
154+
155+
button.nativeElement.click();
156+
157+
expect(windowOpenSpy).toHaveBeenCalledWith(ALTERNATIVE_URL, '_blank');
158+
});
159+
});
160+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/** <%= options.generationDisclaimerText %> **/
2+
3+
import {ChangeDetectionStrategy, Component, computed, input} from '@angular/core';
4+
import {MatTooltipModule} from '@angular/material/tooltip';
5+
import {MatIconModule} from '@angular/material/icon';
6+
import {MatIconButton} from '@angular/material/button';
7+
8+
@Component({
9+
selector: 'esmf-table-cell-link',
10+
templateUrl: './table-cell-link.component.html',
11+
styleUrls: ['./table-cell-link.component.scss'],
12+
changeDetection: ChangeDetectionStrategy.OnPush,
13+
imports: [MatTooltipModule, MatIconModule, MatIconButton],
14+
})
15+
export class TableCellLinkComponent {
16+
value = input.required<string>();
17+
tooltipMessage = input.required<string>();
18+
19+
isDisabled = computed(() => this.value() === '-');
20+
21+
openExternalLink() {
22+
if (this.isDisabled()) return;
23+
window.open(this.value(), '_blank');
24+
}
25+
}

0 commit comments

Comments
 (0)