I'm trying test custom angular components. I want check the components class state. For this a have two options:
Get the component in the scope
Use web components xtag
The problems:
My scope is always empty - I debug in the console I'm using karma to run the tests in the browser.
xtag returns an HtmlElement class so I can't access my properties(cast in dart?)
The codes:
library vader.panel_spec;
import '../_specs.dart';
import "package:vader/components/vader_component.dart";
main(){
describe('Tests for Panel UI',(){
TestBed _;
Scope _scope;
Element ngAppElement;
beforeEach(async(inject((TestBed tb, Scope scope, VmTurnZone zone, TemplateCache cache) {
_ = tb;
_scope = scope;
addToTemplateCache(cache, 'packages/vader/components/panel.html');
addToTemplateCache(cache, 'packages/vader/components/window/window.html');
})));
beforeEachModule((Module module) {
ngAppElement = new DivElement()..attributes['ng-app'] = '';
module
..install(new VaderComponentModule());
module..bind(Node, toValue: ngAppElement);
document.body.append(ngAppElement);
});
afterEach(() {
ngAppElement.remove();
ngAppElement = null;
});
compile(html) {
ngAppElement.setInnerHtml(html, treeSanitizer: new NullTreeSanitizer());
_.compile(ngAppElement);
return ngAppElement.firstChild;
}
it("should collapse when clicked", async((){
Element panel = compile("<v-panel>CollapsedItem</v-panel>");
microLeap();
_.rootScope.apply();
Element contentPanel = panel.querySelector('.content-panel');
expect(contentPanel.classes.contains('collapse'), isFalse);
_.triggerEvent(panel.querySelector('#collapse-icon'), 'click');
_.rootScope.apply();
window.console.debug(_.rootScope.context);
var comp = panel.xtag;
expect(comp.doCollapse, isTrue);
}));
});
}
library ng_specs;
import 'dart:html' hide Animation;
import 'package:angular/angular.dart';
import 'package:angular/mock/module.dart';
import 'package:guinness/guinness_html.dart' as gns;
export 'dart:html' hide Animation;
export 'package:unittest/unittest.dart' hide expect;
export 'package:guinness/guinness_html.dart';
export 'package:mock/mock.dart';
export 'package:di/di.dart';
export 'package:di/dynamic_injector.dart';
export 'package:angular/angular.dart';
export 'package:angular/application.dart';
export 'package:angular/introspection.dart';
export 'package:angular/core/annotation.dart';
export 'package:angular/core/registry.dart';
export 'package:angular/core/module_internal.dart';
export 'package:angular/core_dom/module_internal.dart';
export 'package:angular/core/parser/parser.dart';
export 'package:angular/core/parser/lexer.dart';
export 'package:angular/directive/module.dart';
export 'package:angular/formatter/module.dart';
export 'package:angular/routing/module.dart';
export 'package:angular/animate/module.dart';
export 'package:angular/mock/module.dart';
export 'package:perf_api/perf_api.dart';
es(String html) {
var div = new DivElement();
div.setInnerHtml(html, treeSanitizer: new NullTreeSanitizer());
return new List.from(div.nodes);
}
e(String html) => es(html).first;
Expect expect(actual, [matcher]) {
final expect = new Expect(actual);
if (matcher != null) {
expect.to(matcher);
}
return expect;
}
class Expect extends gns.Expect {
Expect(actual) : super(actual);
NotExpect get not => new NotExpect(actual);
toBeValid() => _expect(actual.valid && !actual.invalid, true,
reason: 'Form is not valid');
toBePristine() => _expect(actual.pristine && !actual.dirty, true,
reason: 'Form is dirty');
get _expect => gns.guinness.matchers.expect;
}
class NotExpect extends gns.NotExpect {
NotExpect(actual) : super(actual);
toBeValid() => _expect(actual.valid && !actual.invalid, false,
reason: 'Form is valid');
toBePristine() => _expect(actual.pristine && !actual.dirty, false,
reason: 'Form is pristine');
get _expect => gns.guinness.matchers.expect;
}
_injectify(fn) {
// The function does two things:
// First: if the it() passed a function, we wrap it in
// the "sync" FunctionComposition.
// Second: when we are calling the FunctionComposition,
// we inject "inject" into the middle of the
// composition.
if (fn is! FunctionComposition) fn = sync(fn);
return fn.outer(inject(fn.inner));
}
// Replace guinness syntax elements to inject dependencies.
beforeEachModule(fn) => gns.beforeEach(module(fn), priority:1);
beforeEach(fn) => gns.beforeEach(_injectify(fn));
afterEach(fn) => gns.afterEach(_injectify(fn));
it(name, fn) => gns.it(name, _injectify(fn));
iit(name, fn) => gns.iit(name, _injectify(fn));
_removeNgBinding(node) {
if (node is Element) {
node = node.clone(true) as Element;
node.classes.remove('ng-binding');
node.querySelectorAll(".ng-binding").forEach((Element e) {
e.classes.remove('ng-binding');
});
return node;
}
return node;
}
/**
* It adds an html template into the TemplateCache.
*/
void addToTemplateCache(TemplateCache cache, String path) {
HttpRequest request = new HttpRequest();
request.open("GET", path, async : false);
request.send();
cache.put(path, new HttpResponse(200, request.responseText));
}
main() {
gns.beforeEach(setUpInjector, priority:3);
gns.afterEach(tearDownInjector);
gns.guinnessEnableHtmlMatchers();
gns.guinness.matchers.config.preprocessHtml = _removeNgBinding;
}
Use ngInjector(panel).get(PanelComponent) or ngDirectives(panel)[0]. See the documentation here.
Related
I'm attempting to use playwright to automate an electron js application, but I can't seem to find any relevant information. To automate a simple programme, I used playwright:- https://playwright.dev/docs/api/class-electron and https://www.electronjs.org/docs/latest/tutorial/quick-start. However, I am unable to obtain the elements (buttons, dropdowns, and so on) in the electron application. Any reference or documentation that will deeply guide me to automate desktop application using playwright.
I got mine to work using their intro guide
for me since the installer installs additional components, i had to build and install, then supply the path to the exe
in my package.json i have.
"playwright": "^1.25.0",
"#playwright/test": "^1.25.0",
"eslint-plugin-playwright": "^0.10.0",
I created this class to help me have a cleaner code.
import { _electron as electron, ElectronApplication, Page } from 'playwright';
class ElectronAppController {
static electronApp: ElectronApplication;
static window1: Page;
static window2: Page;
static window3: Page;
static async launchApp() {
ElectronAppController.electronApp = await electron.launch({
executablePath: 'C:\\pathTo\\app.exe',
});
ElectronAppController.electronApp.on('window', async (page) => {
ElectronAppController.assignWindows(page);
});
const mywindows: Page[] =
await ElectronAppController.electronApp.windows();
for (
let index = 0, l = mywindows.length;
index < l;
index += 1
) {
ElectronAppController.assignWindows(
mywindows[index]
);
}
}
private static assignWindows(page: Page) {
const myurl = path.basename(page.url());
if (myurl === 'window1.html') {
ElectronAppController.window1= page;
}
if (myurl === 'window2.html') {
ElectronAppController.window2= page;
}
if (myurl === 'window3.html') {
ElectronAppController.window3= page;
}
return true;
}
}
the test file name should be [name].spec.ts, don't forget to import
test.describe('First Window Tests', async () => {
test.beforeAll(async () => {
await ElectronAppController.launchApp();
});
test('Check if first window opened', didLaunchApp);
test('name of the test', async () => {
// test body
// this will allow you to record a test very useful, but sometimes it has some problems check note bellow
await ElectronAppController.window1.pause;
});
test.afterAll(async () => {
await ElectronAppController.electronApp.close();
});
});
here is a didLaunchApp just as a simple test
const didLaunchApp = async () => {
const isVisible: boolean = await ElectronAppController.electronApp.evaluate(
async ({ BrowserWindow }) => {
const mainWindow = BrowserWindow.getAllWindows()[0];
const getState = () => mainWindow.isVisible();
return new Promise((resolve) => {
if (mainWindow.isVisible()) {
resolve(getState());
} else {
mainWindow.once('ready-to-show', () => {
setTimeout(() => resolve(getState()), 0);
});
}
});
}
);
await expect(isVisible).toBeTruthy();
};
you can record tests but sometimes that might make some problems if you have some popups or other effects on hovering over an element,
you can read more about selectors here
I'm just finishing a series of e2e tests using the same as you, Electron with React. What you don't see? Does it at least load the application?
Share the code of one test and how you launch using .launch method.
I am using Quasar with API composition and I have an issue.
I just want to get parameters from the current route so within onMounted, I try to use this.$route.params, but I always get "this is undefined" in console.
// src/pages/Level.vue
import { ref, onMounted, computed } from "vue";
import { useStore } from 'vuex'
import { useQuasar } from 'quasar';
export default {
name: "LevelPage",
setup () {
const $q = useQuasar();
const $store = useStore();
const level = ref(null);
onMounted(()=> {
console.log(this.$route.params)
});
return {
level,
}
},
};
I get the same error every time I use this in the setup.
So there must be something I'm not getting. Can you please help me?
I think you are looking for https://v3.vuejs.org/guide/composition-api-provide-inject.html#mutating-reactive-properties In your Level.vue:
import { provide } from 'vue';
export default {
setup() {
provide('appName', 'vue3')
}
}
And then in any child component where you want to access this variable, inject it:
import { inject } from 'vue'
export default {
setup() {
const appName = inject('appName');
}
}
OR
import { useRoute } from 'vue-router'
export default {
setup() {
const route = useRoute()
onMounted(() => {
const id = route.params.id
})
}
}
https://ant.design/components/transfer/
Hey all! I was just wondering is it possible to implement two separate functions on the transfer buttons. For example, I want to run add function when the user clicks transfer to the right and I want to add remove function when the user clicks transfer button to the left.
From the documentation all I could see was both the buttons just trigger onChange function and I dont want that.
The API of Transfer component uses only one function to change the data, but you can call different functions inside onChange depending on the direction:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Transfer } from "antd";
const mockData = [];
for (let i = 0; i < 20; i++) {
mockData.push({
key: i.toString(),
title: `content${i + 1}`,
description: `description of content${i + 1}`
});
}
const initialTargetKeys = mockData
.filter((item) => +item.key > 10)
.map((item) => item.key);
const App = () => {
const [targetKeys, setTargetKeys] = useState(initialTargetKeys);
const [selectedKeys, setSelectedKeys] = useState([]);
const handleAdd = (nextTargetKeys, moveKeys) => {
console.log("add");
setTargetKeys(nextTargetKeys);
};
const handleDelete = (nextTargetKeys, moveKeys) => {
console.log("delete");
setTargetKeys(nextTargetKeys);
};
const onChange = (nextTargetKeys, direction, moveKeys) => {
if (direction === "left") {
handleDelete(nextTargetKeys, moveKeys);
} else {
handleAdd(nextTargetKeys, moveKeys);
}
};
const onSelectChange = (sourceSelectedKeys, targetSelectedKeys) => {
setSelectedKeys([...sourceSelectedKeys, ...targetSelectedKeys]);
};
return (
<Transfer
dataSource={mockData}
titles={["Source", "Target"]}
targetKeys={targetKeys}
selectedKeys={selectedKeys}
onChange={onChange}
onSelectChange={onSelectChange}
render={(item) => item.title}
/>
);
};
ReactDOM.render(<App />, document.getElementById("container"));
I'm working on a JHipster application that I'm trying to get functioning in Electron. I have Golden Layout for window/pane management and cross-pane communication. I am having several problems with the combination of technologies, including:
I can't pop out more than one pane at the same time into their own Electron windows. I instead get an Uncaught Error: Can't create config, layout not yet initialised error in the console.
Two thirds of the panes don't display anything when popped out into Electron windows, and I'm not sure what the reason is. Any ideas or suggestions for this? One example of content is a leaflet map, another is a "PowerPoint preview" that is really just divs that mock the appearance of slides.
I haven't made it this far yet, but I assume that I will have trouble communicating between popped-out Electron windows when I get more than one open. Right now, the panes communicate between each other using Golden Layout's glEventHub emissions. I have an avenue to explore when I cross that bridge, namely Electron ipcRenderer.
Some borrowed code is here (most of it I can't share because it's company confidential):
electron.js:
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const isDev = require('electron-is-dev');
let mainWindow;
function createWindow() {
mainWindow = new BrowserWindow({width: 900, height: 680});
mainWindow.loadURL(isDev ? 'http://localhost:9000' : `file://${path.join(__dirname, '../build/index.html')}`);
if (isDev) {
// Open the DevTools.
//BrowserWindow.addDevToolsExtension('<location to your react chrome extension>');
mainWindow.webContents.openDevTools();
}
mainWindow.on('closed', () => mainWindow = null);
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
goldenLayoutComponent.tsx, a patch for Golden Layout:
import React from "react";
import ReactDOM from "react-dom";
// import "./goldenLayout-dependencies";
import GoldenLayout from "golden-layout";
import "golden-layout/src/css/goldenlayout-base.css";
import "golden-layout/src/css/goldenlayout-dark-theme.css";
import $ from "jquery";
interface IGoldenLayoutProps {
htmlAttrs: {},
config: any,
registerComponents: Function
}
interface IGoldenLayoutState {
renderPanels: Set<any>
}
interface IContainerRef {
current: any
}
export class GoldenLayoutComponent extends React.Component <IGoldenLayoutProps, IGoldenLayoutState> {
goldenLayoutInstance = undefined;
state = {
renderPanels: new Set<any>()
};
containerRef: IContainerRef = React.createRef();
render() {
const panels = Array.from(this.state.renderPanels || []);
return (
<div ref={this.containerRef as any} {...this.props.htmlAttrs}>
{panels.map((panel, index) => {
return ReactDOM.createPortal(
panel._getReactComponent(),
panel._container.getElement()[0]
);
})}
</div>
);
}
componentRender(reactComponentHandler) {
this.setState(state => {
const newRenderPanels = new Set(state.renderPanels);
newRenderPanels.add(reactComponentHandler);
return { renderPanels: newRenderPanels };
});
}
componentDestroy(reactComponentHandler) {
this.setState(state => {
const newRenderPanels = new Set(state.renderPanels);
newRenderPanels.delete(reactComponentHandler);
return { renderPanels: newRenderPanels };
});
}
componentDidMount() {
this.goldenLayoutInstance = new GoldenLayout(
this.props.config || {},
this.containerRef.current
);
if (this.props.registerComponents instanceof Function)
this.props.registerComponents(this.goldenLayoutInstance);
this.goldenLayoutInstance.reactContainer = this;
this.goldenLayoutInstance.init();
}
}
// Patching internal GoldenLayout.__lm.utils.ReactComponentHandler:
const ReactComponentHandler = GoldenLayout["__lm"].utils.ReactComponentHandler;
class ReactComponentHandlerPatched extends ReactComponentHandler {
_container: any;
_reactClass: any;
_render() {
const reactContainer = this._container.layoutManager.reactContainer; // Instance of GoldenLayoutComponent class
if (reactContainer && reactContainer.componentRender)
reactContainer.componentRender(this);
}
_destroy() {
// ReactDOM.unmountComponentAtNode( this._container.getElement()[ 0 ] );
this._container.off("open", this._render, this);
this._container.off("destroy", this._destroy, this);
}
_getReactComponent() {
// the following method is absolute copy of the original, provided to prevent depenency on window.React
const defaultProps = {
glEventHub: this._container.layoutManager.eventHub,
glContainer: this._container
};
const props = $.extend(defaultProps, this._container._config.props);
return React.createElement(this._reactClass, props);
}
}
GoldenLayout["__lm"].utils.ReactComponentHandler = ReactComponentHandlerPatched;
Any help or insight into these issues would be appreciated. Thanks in advance!
If you are still looking for a solutions, 1 and 2 I have solved, if you want to see my solution you could see in this repository.
But it was basically this:
1: The window that popups has a different path than the main window, so I just had to put a try catch in my requires, and you have to set
nativeWindowOpen = true
when creating the Browser window.
2: Solves it's self after 1 I think
I have a table that contains a collection of files.
My use case is that when a user clicks on a row in that table, an event will be fired that will make a call to the service and get the JSON data. the JSON data is going to change whenever user clicks on a different row in a table. So I am not going to have specific properties or let's say keys (in key value pair). I am just gonna get JSON data in the form of a 2D string array.
Now my task is to grab this data and export it into excel document.
I was able to find a solution to this problem and I am posting it here. Here are the links I had referred to in order to make this work.
https://stackblitz.com/edit/angular6-export-xlsx
https://medium.com/#madhavmahesh/exporting-an-excel-file-in-angular-927756ac9857
The first thing to do is
npm install xlsx
Then create a service -->
import { Injectable } from '#angular/core';
import * as FileSaver from 'file-saver';
import * as XLSX from 'xlsx';
const EXCEL_TYPE =
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8';
const EXCEL_EXTENSION = '.xlsx';
#Injectable()
export class ExcelService {
constructor() {}
public exportAsExcelFile(json: any[], excelFileName: string): void {
const worksheet: XLSX.WorkSheet = XLSX.utils.json_to_sheet(json);
console.log('worksheet', worksheet);
const workbook: XLSX.WorkBook = { Sheets: { data: worksheet }, SheetNames: ['data'] };
const excelBuffer: any = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' });
// const excelBuffer: any = XLSX.write(workbook, { bookType: 'xlsx', type: 'buffer' });
this.saveAsExcelFile(excelBuffer, excelFileName);
}
private saveAsExcelFile(buffer: any, fileName: string): void {
const data: Blob = new Blob([buffer], {
type: EXCEL_TYPE
});
FileSaver.saveAs(data, fileName + '_export_' + new Date().getTime() + EXCEL_EXTENSION);
}
}
Import this service in your main component (app.module.ts or whatever your component is). Make sure you add that service in the 'providers' array.
Then in your TS file, add this function -->
exportAsXLSX(): void {
this.someService.getFileData(this.clientFile).subscribe(
(res: any) => {
if (res.data) {
this.fileData = res.data;
}
},
err => {
let someMsg = 'Error exporting file';
if (err.status === 409) {
someMsg =
err.error.error.errorMessage.length > 0
? err.error.error.errorMessage
: someMsg;
}
this.toastService.error(someMsg);
}
);
this.excelService.exportAsExcelFile(this.fileData, 'sample');
}
Here, this.fileData is an array of type any, defined as fileData: any [] in my component. It's totally up to you how to get data in this array. The main thing is to pass it to the function that will convert this to excel.
Now that you have your service written and your component has a function for it, it's time to wire this up to the HTML file.
<div class="ui-helper-clearfix">
<button
type="button"
pButton
icon="fa fa-file-excel-o"
iconPos="left"
label="Export To Excel"
(click)="exportAsXLSX()"
style="float:left"
></button>
</div>
On click of the button, you will be able to get data in an excel document.