Skip to content

Commit

Permalink
apps portal sdks
Browse files Browse the repository at this point in the history
  • Loading branch information
nirgur committed Sep 29, 2024
1 parent 45e068d commit 424d510
Show file tree
Hide file tree
Showing 11 changed files with 488 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ApplicationsPortalComponent } from './applications-portal.component';
import createSdk from '@descope/web-js-sdk';
import { DescopeAuthConfig } from '../../types/types';
import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core';
import mocked = jest.mocked;

jest.mock('@descope/web-js-sdk');
//Mock DescopeApplicationsPortalWidget
jest.mock('@descope/applications-portal-widget', () => {
return jest.fn(() => {
// Create a mock DOM element
return document.createElement('descope-applications-portal-widget');
});
});

describe('DescopeApplicationsPortalComponent', () => {
let component: ApplicationsPortalComponent;
let fixture: ComponentFixture<ApplicationsPortalComponent>;
let mockedCreateSdk: jest.Mock;
const onSessionTokenChangeSpy = jest.fn();
const onAuditChangeSpy = jest.fn();
const afterRequestHooksSpy = jest.fn();
const mockConfig: DescopeAuthConfig = {
projectId: 'someProject'
};

beforeEach(() => {
mockedCreateSdk = mocked(createSdk);

mockedCreateSdk.mockReturnValue({
onSessionTokenChange: onSessionTokenChangeSpy,
onAuditChange: onAuditChangeSpy,
httpClient: {
hooks: {
afterRequest: afterRequestHooksSpy
}
}
});

TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
DescopeAuthConfig,
{ provide: DescopeAuthConfig, useValue: mockConfig }
]
});

fixture = TestBed.createComponent(ApplicationsPortalComponent);
component = fixture.componentInstance;
component.projectId = '123';
component.widgetId = 'widget-1';
component.logout = new EventEmitter<CustomEvent>();
component.logger = {
info: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
debug: jest.fn()
};
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
const html: HTMLElement = fixture.nativeElement;
const webComponentHtml = html.querySelector(
'descope-applications-portal-widget'
);
expect(webComponentHtml).toBeDefined();
});

it('should correctly setup attributes based on inputs', () => {
const html: HTMLElement = fixture.nativeElement;
const webComponentHtml = html.querySelector(
'descope-applications-portal-widget'
)!;
expect(webComponentHtml.getAttribute('project-id')).toStrictEqual('123');
expect(webComponentHtml.getAttribute('widget-id')).toStrictEqual(
'widget-1'
);
expect(webComponentHtml.getAttribute('logger')).toBeDefined();
});

it('should emit logout when web component emits logout', () => {
const html: HTMLElement = fixture.nativeElement;
const webComponentHtml = html.querySelector(
'descope-applications-portal-widget'
)!;

const event = {
detail: 'logout'
};
component.logout.subscribe((e) => {
expect(afterRequestHooksSpy).toHaveBeenCalled();
expect(e.detail).toHaveBeenCalledWith(event.detail);
});
webComponentHtml.dispatchEvent(new CustomEvent('logout', event));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnInit,
Output
} from '@angular/core';
import DescopeApplicationsPortalWidget from '@descope/applications-portal-widget';
import { ILogger } from '@descope/web-component';
import { DescopeAuthConfig } from '../../types/types';

@Component({
selector: 'applications-portal',
standalone: true,
template: ''
})
export class ApplicationsPortalComponent implements OnInit, OnChanges {
projectId: string;
baseUrl?: string;
baseStaticUrl?: string;
@Input() widgetId: string;

@Input() theme: 'light' | 'dark' | 'os';
@Input() debug: boolean;
@Input() logger: ILogger;

@Output() logout: EventEmitter<CustomEvent> = new EventEmitter<CustomEvent>();

private readonly webComponent = new DescopeApplicationsPortalWidget();

constructor(
private elementRef: ElementRef,
descopeConfig: DescopeAuthConfig
) {
this.projectId = descopeConfig.projectId;
this.baseUrl = descopeConfig.baseUrl;
this.baseStaticUrl = descopeConfig.baseStaticUrl;
}

ngOnInit() {
this.setupWebComponent();
this.elementRef.nativeElement.appendChild(this.webComponent);
}

ngOnChanges(): void {
this.setupWebComponent();
}

private setupWebComponent() {
this.webComponent.setAttribute('project-id', this.projectId);
this.webComponent.setAttribute('widget-id', this.widgetId);
if (this.baseUrl) {
this.webComponent.setAttribute('base-url', this.baseUrl);
}
if (this.baseStaticUrl) {
this.webComponent.setAttribute('base-static-url', this.baseStaticUrl);
}
if (this.theme) {
this.webComponent.setAttribute('theme', this.theme);
}
if (this.debug) {
this.webComponent.setAttribute('debug', this.debug.toString());
}

if (this.logger) {
(this.webComponent as any).logger = this.logger;
}

if (this.logout) {
this.webComponent.addEventListener('logout', (e: Event) => {
this.logout?.emit(e as CustomEvent);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<applications-portal
[theme]="theme"
[debug]="debugMode"
widgetId="applications-portal-widget"
/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyApplicationsPortalComponent } from './my-applications-portal.component';
import createSdk from '@descope/web-js-sdk';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types';
import mocked = jest.mocked;

jest.mock('@descope/web-js-sdk');

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

let mockedCreateSdk: jest.Mock;

beforeEach(() => {
mockedCreateSdk = mocked(createSdk);
mockedCreateSdk.mockReturnValue({});

TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
declarations: [MyApplicationsPortalComponent],
providers: [
DescopeAuthConfig,
{ provide: DescopeAuthConfig, useValue: { projectId: 'test' } }
]
});
fixture = TestBed.createComponent(MyApplicationsPortalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { environment } from '../../environments/environment';
import { Router } from '@angular/router';

@Component({
selector: 'app-my-applications-portal',
templateUrl: './my-applications-portal.component.html',
styleUrls: ['./my-applications-portal.scss']
})
export class MyApplicationsPortalComponent {
theme = (environment.descopeTheme as 'light' | 'dark' | 'os') ?? 'os';
debugMode = environment.descopeDebugMode ?? false;

constructor(private router: Router) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
:host {
height: 100vh;
position: relative;
}
main {
border-radius: 10px;
margin: auto;
border: 1px solid lightgray;
padding: 20px;
max-width: 700px;
box-shadow:
13px 13px 20px #cbced1,
-13px -13px 20px #fff;
background: #ecf0f3;
position: relative;
top: 50%;
transform: translateY(-50%);
}
68 changes: 68 additions & 0 deletions packages/sdks/react-sdk/examples/app/MyApplicationsPortal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type { UserResponse } from '@descope/web-js-sdk';
import React, { useCallback } from 'react';
import { Link } from 'react-router-dom';
import { useDescope, useUser, ApplicationsPortal } from '../../src';

const getUserDisplayName = (user?: UserResponse) =>
user?.name || user?.loginIds?.[0] || '';

const MyApplicationsPortal = () => {
// useUser retrieves the logged in user information
const { user } = useUser();
// useDescope retrieves Descope SDK for further operations related to authentication
// such as logout
const sdk = useDescope();

const onLogout = useCallback(() => {
sdk.logout();
}, [sdk]);

return (
<>
<header
style={{
borderBottom: '1px solid gray',
display: 'flex',
justifyContent: 'space-between',
}}
>
<p>
<a href="/">Home</a>
</p>
<div>
<p>
User:{' '}
<span style={{ fontWeight: 'bold' }}>
{getUserDisplayName(user)}
</span>
</p>
<p>
<button
type="button"
id="logout-button"
onClick={onLogout}
style={{
display: 'block',
marginLeft: 'auto',
padding: 5,
}}
>
Logout
</button>
</p>
<p>
{process.env.DESCOPE_STEP_UP_FLOW_ID && (
<Link id="step-up-button" to="/step-up">
Step Up
</Link>
)}
</p>
</div>
</header>
<h2>My Profile</h2>
<ApplicationsPortal widgetId="applications-portal-widget" />
</>
);
};

export default MyApplicationsPortal;
Loading

0 comments on commit 424d510

Please sign in to comment.