Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CardView - implement OptionsController #28523

Closed
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
@@ -0,0 +1,7 @@
import { OptionsController } from '@ts/grids/new/grid_core/options_controller/options_controller_base';

import type { defaultOptions, Options } from './options';

class CardViewOptionsController extends OptionsController<Options, typeof defaultOptions> {}

export { CardViewOptionsController as OptionsController };
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,21 @@
import registerComponent from '@js/core/component_registrator';
import $ from '@js/core/renderer';
import { MainView as MainViewBase } from '@ts/grids/new/grid_core/main_view';
import { OptionsController as OptionsControllerBase } from '@ts/grids/new/grid_core/options_controller/options_controller';
import { GridCoreNew } from '@ts/grids/new/grid_core/widget';

import { MainView } from './main_view';
import { defaultOptions } from './options';
import { OptionsController } from './options_controller';

export class CardViewBase extends GridCoreNew {
protected _registerDIContext(): void {
super._registerDIContext();
this.diContext.register(MainViewBase, MainView);

const optionsController = new OptionsController(this);
this.diContext.registerInstance(OptionsController, optionsController);
this.diContext.registerInstance(OptionsControllerBase, optionsController);
}

protected _initMarkup(): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Properties as ButtonProperties } from '@js/ui/button';
import dxButton from '@js/ui/button';

import { InfernoWrapper } from './widget_wrapper';

export class Button extends InfernoWrapper<ButtonProperties, dxButton> {
protected getComponentFabric(): typeof dxButton {
return dxButton;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/ban-types */
import type { dxElementWrapper } from '@js/core/renderer';
import $ from '@js/core/renderer';
import { Component, createRef } from 'inferno';

interface TemplateType<T> {
render: (args: { model: T; container: dxElementWrapper }) => void;
}

// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types
export function TemplateWrapper<TProps = {}>(template: TemplateType<TProps>) {
return class Template extends Component<TProps> {
private readonly ref = createRef<HTMLDivElement>();

private renderTemplate(): void {
$(this.ref.current!).empty();
template.render({
container: $(this.ref.current!),
model: this.props,
});
}

public render(): JSX.Element {
return <div ref={this.ref}></div>;
}

public componentDidUpdate(): void {
this.renderTemplate();
}

public componentDidMount(): void {
this.renderTemplate();
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type DOMComponent from '@js/core/dom_component';
import type { InfernoNode, RefObject } from 'inferno';
import { Component, createRef } from 'inferno';

interface WithRef<TComponent> {
componentRef?: RefObject<TComponent>;
}

export abstract class InfernoWrapper<
TProperties,
TComponent extends DOMComponent<TProperties>,
> extends Component<TProperties & WithRef<TComponent>> {
protected readonly ref = createRef<HTMLDivElement>();

protected component?: TComponent;

protected abstract getComponentFabric(): new (
element: Element, options: TProperties
) => TComponent;

public render(): InfernoNode {
return <div ref={this.ref}></div>;
}

private updateComponentRef(): void {
if (this.props.componentRef) {
// @ts-expect-error
this.props.componentRef.current = this.component;
}
}

protected updateComponentOptions(prevProps: TProperties, props: TProperties): void {
Object.keys(props as object).forEach((key) => {
if (props[key] !== prevProps[key]) {
this.component?.option(key, props[key]);
}
});
}

public componentDidMount(): void {
// eslint-disable-next-line no-new, @typescript-eslint/no-non-null-assertion
this.component = new (this.getComponentFabric())(this.ref.current!, this.props);
this.updateComponentRef();
}

public componentDidUpdate(prevProps: TProperties): void {
this.updateComponentOptions(prevProps, this.props);
this.updateComponentRef();
}

public componentWillUnmount(): void {
this.component?.dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { defaultOptions, Options } from '../options';
import { OptionsControllerMock as OptionsControllerBaseMock } from './options_controller_base.mock';

export class OptionsControllerMock extends OptionsControllerBaseMock<
Options, typeof defaultOptions
> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* eslint-disable @typescript-eslint/ban-types */
import type { defaultOptions, Options } from '../options';
import { OptionsController as OptionsControllerBase } from './options_controller_base';

class GridCoreOptionsController extends OptionsControllerBase<Options, typeof defaultOptions> {}

export { GridCoreOptionsController as OptionsController };
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/* eslint-disable max-classes-per-file */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable max-len */
/* eslint-disable spellcheck/spell-checker */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Component } from '@js/core/component';

import { OptionsController } from './options_controller_base';

export class OptionsControllerMock<
TProps,
TDefaultProps extends TProps,
> extends OptionsController<TProps, TDefaultProps> {
private readonly componentMock: Component<TProps>;
constructor(options: TProps) {
const componentMock = new Component(options);
super(componentMock);
this.componentMock = componentMock;
}

public option(key?: string, value?: unknown): unknown {
// @ts-expect-error
return this.componentMock.option(key, value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/* eslint-disable spellcheck/spell-checker */
/* eslint-disable @typescript-eslint/init-declarations */
import {
beforeEach,
describe, expect, it, jest,
} from '@jest/globals';
import { Component } from '@js/core/component';

import { OptionsController } from './options_controller_base';

interface Options {
value?: string;

objectValue?: {
nestedValue?: string;
};

onOptionChanged?: () => void;
}

const onOptionChanged = jest.fn();
let component: Component<Options>;
let optionsController: OptionsController<Options>;

beforeEach(() => {
component = new Component<Options>({
value: 'initialValue',
objectValue: { nestedValue: 'nestedInitialValue' },
onOptionChanged,
});
optionsController = new OptionsController<Options>(component);
onOptionChanged.mockReset();
});

describe('oneWay', () => {
describe('plain', () => {
it('should have initial value', () => {
const value = optionsController.oneWay('value');
expect(value.unreactive_get()).toBe('initialValue');
});

it('should update on options changed', () => {
const value = optionsController.oneWay('value');
const fn = jest.fn();

value.subscribe(fn);

component.option('value', 'newValue');
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenCalledWith('newValue');
});
});

describe('nested', () => {
it('should have initial value', () => {
const a = optionsController.oneWay('objectValue.nestedValue');
expect(a.unreactive_get()).toBe('nestedInitialValue');
});
});
});

describe('twoWay', () => {
it('should have initial value', () => {
const value = optionsController.twoWay('value');
expect(value.unreactive_get()).toBe('initialValue');
});

it('should update on options changed', () => {
const value = optionsController.twoWay('value');
const fn = jest.fn();

value.subscribe(fn);

component.option('value', 'newValue');
expect(fn).toHaveBeenCalledTimes(2);
expect(fn).toHaveBeenCalledWith('newValue');
});

it('should return new value after update', () => {
const value = optionsController.twoWay('value');
value.update('newValue');

expect(value.unreactive_get()).toBe('newValue');
});

it('should call optionChanged on update', () => {
const value = optionsController.twoWay('value');
value.update('newValue');

expect(onOptionChanged).toHaveBeenCalledTimes(1);
expect(onOptionChanged).toHaveBeenCalledWith({
component,
fullName: 'value',
name: 'value',
previousValue: 'initialValue',
value: 'newValue',
});
});
});
Loading
Loading