Skip to content

Commit

Permalink
feat: support YMapLayer component (#249)
Browse files Browse the repository at this point in the history
  • Loading branch information
ddubrava authored Jul 20, 2024
1 parent 6ce4381 commit 03d14dd
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 0 deletions.
1 change: 1 addition & 0 deletions libs/angular-yandex-maps-v3/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export * from './lib/components/controls/y-map-scale-control/y-map-scale-control
export * from './lib/components/controls/y-map-zoom-control/y-map-zoom-control.directive';
export * from './lib/components/layers/y-map-default-features-layer/y-map-default-features-layer.directive';
export * from './lib/components/layers/y-map-default-scheme-layer/y-map-default-scheme-layer.directive';
export * from './lib/components/layers/y-map-layer/y-map-layer.directive';

// Services
export * from './lib/services/y-api-loader/y-api-loader.service';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Component, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { YMapLayerProps } from '@yandex/ymaps3-types';
import { BehaviorSubject } from 'rxjs';

import {
mockYMapInstance,
mockYMapLayerConstructor,
mockYMapLayerInstance,
} from '../../../../test-utils';
import { YReadyEvent } from '../../../types/y-ready-event';
import { YMapComponent } from '../../common/y-map/y-map.component';
import { YMapLayerDirective } from './y-map-layer.directive';

@Component({
standalone: true,
imports: [YMapLayerDirective],
template: '<y-map-layer [props]="props" />',
})
class MockHostComponent {
@ViewChild(YMapLayerDirective, { static: true })
layer!: YMapLayerDirective;

props: YMapLayerProps = {
type: 'markers',
};
}

describe('YMapLayerDirective', () => {
let component: YMapLayerDirective;
let mockComponent: MockHostComponent;
let fixture: ComponentFixture<MockHostComponent>;

let mapInstance: ReturnType<typeof mockYMapInstance>;
let layerInstance: ReturnType<typeof mockYMapLayerInstance>;
let layerConstructorMock: jest.Mock;

beforeEach(async () => {
mapInstance = mockYMapInstance();

await TestBed.configureTestingModule({
imports: [MockHostComponent],
providers: [
{
provide: YMapComponent,
useValue: {
map$: new BehaviorSubject(mapInstance),
},
},
],
}).compileComponents();

fixture = TestBed.createComponent(MockHostComponent);
mockComponent = fixture.componentInstance;
component = mockComponent.layer;

layerInstance = mockYMapLayerInstance();
layerConstructorMock = mockYMapLayerConstructor(layerInstance);
});

afterEach(() => {
(window as any).ymaps3 = undefined;
});

it('should create entity', () => {
fixture.detectChanges();

expect(layerConstructorMock).toHaveBeenCalledWith(mockComponent.props);
expect(mapInstance.addChild).toHaveBeenCalledWith(layerInstance);
});

it('should emit ready on load', () => {
jest.spyOn(component.ready, 'emit');
fixture.detectChanges();

const readyEvent: YReadyEvent = {
ymaps3: (window as any).ymaps3,
entity: layerInstance,
};

expect(component.ready.emit).toHaveBeenCalledWith(readyEvent);
});

it('should pass inputs to constructor', () => {
const props: YMapLayerProps = {
type: 'ground',
};

mockComponent.props = props;

fixture.detectChanges();

expect(layerConstructorMock).toHaveBeenCalledWith(props);
});

it('should update props input after init', () => {
fixture.detectChanges();

const props: YMapLayerProps = {
type: 'features',
zIndex: 10,
};

mockComponent.props = props;

fixture.detectChanges();

expect(layerInstance.update).toHaveBeenCalledWith(props);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {
Directive,
EventEmitter,
Input,
OnChanges,
OnDestroy,
OnInit,
Output,
SimpleChanges,
} from '@angular/core';
import { YMapLayer, YMapLayerProps } from '@yandex/ymaps3-types';
import { Subject, takeUntil } from 'rxjs';
import { filter } from 'rxjs/operators';

import { YReadyEvent } from '../../../types/y-ready-event';
import { YMapComponent } from '../../common/y-map/y-map.component';

/**
* This component wraps the [ymaps3.YMapLayer](https://yandex.ru/dev/jsapi30/doc/ru/ref/#class-ymaplayer) class from the Yandex.Maps API.
* All component inputs are named the same as the API class constructor arguments.
*
* ```html
* <y-map
* [props]="{
* location: {
* center: [-0.127696, 51.507351],
* zoom: 10,
* },
* }"
* >
* <y-map-default-scheme-layer />
*
* <y-map-layer
* [props]="{
* type: 'markers',
* zIndex: 1800,
* }"
* />
* </y-map>
* ```
*/
@Directive({
selector: 'y-map-layer',
standalone: true,
})
export class YMapLayerDirective implements OnInit, OnDestroy, OnChanges {
private readonly destroy$ = new Subject<void>();

private layer?: YMapLayer;

/**
* See the API entity documentation for detailed information. Supports ngOnChanges.
* {@link https://yandex.ru/dev/jsapi30/doc/ru/ref/#YMapLayerProps}
*/
@Input({ required: true }) props!: YMapLayerProps;

/**
* The entity instance is created. This event runs outside an Angular zone.
*/
@Output() ready: EventEmitter<YReadyEvent<YMapLayer>> = new EventEmitter<
YReadyEvent<YMapLayer>
>();

constructor(private readonly yMapComponent: YMapComponent) {}

ngOnInit() {
this.yMapComponent.map$.pipe(filter(Boolean), takeUntil(this.destroy$)).subscribe((map) => {
this.layer = new ymaps3.YMapLayer(this.props);
map.addChild(this.layer);
this.ready.emit({ ymaps3, entity: this.layer });
});
}

ngOnChanges(changes: SimpleChanges) {
if (this.layer) {
this.layer.update(changes['props'].currentValue);
}
}

ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
30 changes: 30 additions & 0 deletions libs/angular-yandex-maps-v3/src/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface TestingWindow extends Window {
YMapFeature?: jest.Mock;
YMapGeolocationControl?: jest.Mock;
YMapGroupEntity?: jest.Mock;
YMapLayer?: jest.Mock;
YMapListener?: jest.Mock;
YMapMarker?: jest.Mock;
YMapOpenMapsButton?: jest.Mock;
Expand Down Expand Up @@ -97,6 +98,35 @@ export const mockYMapConstructor = (instance: ReturnType<typeof mockYMapInstance
return constructorMock;
};

/**
* Mocks a ymaps3.YMapLayer instance.
*/
export const mockYMapLayerInstance = () => ({
update: jest.fn(),
});

/**
* Mocks a ymaps3.YMapLayer class.
* @param instance instance that is returned from a constructor.
*/
export const mockYMapLayerConstructor = (
instance: ReturnType<typeof mockYMapLayerInstance>,
): jest.Mock => {
const constructorMock = jest.fn(() => instance);

const testingWindow: TestingWindow = window;

if (testingWindow.ymaps3) {
testingWindow.ymaps3.YMapLayer = constructorMock;
} else {
testingWindow.ymaps3 = {
YMapLayer: constructorMock,
};
}

return constructorMock;
};

/**
* Mocks a ymaps3.YMapDefaultFeaturesLayerDirective instance.
*/
Expand Down

0 comments on commit 03d14dd

Please sign in to comment.