loadChildren syntax - what is hash part - javascript

Angular in the docs says the following:
{
path: 'admin',
loadChildren: 'app/admin/admin.module#AdminModule',
},
I'm curious about the syntax. As I understand, this part:
app/admin/admin.module
defines the path to load a module. But what is this #AdminModule?
I'm reading this article, and there is the following path:
loadChildren: 'contacts.bundle.js',
so, as you can see, there is nothing with hash.

The hash part denotes the exported module name. So, inside app/admin/admin.module the AdminModule is exported:
export class AdminModule {}
However, if default export is used, there is no need to use hash.
Here is the relevant part from the sources system_js_ng_module_factory_loader.ts:
private loadAndCompile(path: string): Promise<NgModuleFactory<any>> {
let [module, exportName] = path.split(_SEPARATOR);
if (exportName === undefined) exportName = 'default';
return System.import(module)
.then((module: any) => module[exportName])
.then((type: any) => checkNotEmpty(type, module, exportName))
.then((type: any) => this._compiler.compileModuleAsync(type));
}

Related

TypeError: _API.default is not a constructor with Jest tests

I have an API class that I am trying to use in a React app.
// API file
class API {
...
}
export default API;
// Other file
import API from "utils/API";
const api = new API();
And I am getting the error:
TypeError: _API.default is not a constructor
But.. it seems like my default is set?
My Jest setup is like this:
"jest": {
"setupFiles": [
"./jestSetupFile.js"
],
"testEnvironment": "jsdom",
"preset": "jest-expo",
"transformIgnorePatterns": [
"node_modules/(?!((jest-)?react-native|#react-native(-community)?)|expo(nent)?|#expo(nent)?/.*|#expo-google-fonts/.*|react-navigation|#react-navigation/.*|#unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|react-router-native/.*|#invertase/react-native-apple-authentication/.*)"
]
},
My strong guess is that this is due to a configuration of my babel, webpack or package.json.
What could be causing this?
Note, I want to be clear, this doesn't happen whatsoever in my main application, only in Jest testing
If I change it to a named export/import, I get this:
TypeError: _API.API is not a constructor
Extremely confusing behavior.
As mentioned by others, it would be helpful to see a minimum reproducible example.
However, there is one other possible cause. Are you mocking the API class in your test file at all? This problem can sometimes happen if a class is mistakenly mocked as an "object" as opposed to a function. An object cannot be instantiated with a "new" operator.
For example, say we have a class file utils/API like so:
class API {
someMethod() {
// Does stuff
}
}
export default API;
The following is an "incorrect" way to mock this class and will throw a TypeError... is not a constructor error if the class is instantiated after the mock has been created.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns an object
return {
someMethod: () => {}
};
})
// This will throw the error
const api = new API();
The following will mock the class as a function and will accept the new operator and will not throw the error.
import API from 'utils/API';
jest.mock('utils/API', () => {
// Returns a function
return jest.fn().mockImplementation(() => ({
someMethod: () => {}
}));
})
// This will not throw an error anymore
const api = new API();
Trying adding "esModuleInterop": true, in your tsconfig.json. BY default esModuleInterop is set to false or is not set. B setting esModuleInterop to true changes the behavior of the compiler and fixes some ES6 syntax errors.
Refer the documentation here.
This was ultimately due to additional code inside the file that I was exporting the class from.
import { store } from "root/App";
if (typeof store !== "undefined") {
let storeState = store.getState();
let profile = storeState.profile;
}
At the top, outside my class for some functionality I had been working on.
This caused the class default export to fail, but only in Jest, not in my actual application.
You'll need to export it like this :
export default class API
You could try with:
utils/API.js
export default class API {
...
}
test.js
import API from "utils/API";
const api = new API();
I'm adding this because the issue I had presented the same but has a slightly different setup.
I'm not exporting the class with default, i.e.
MyClass.ts
// with default
export default MyClass {
public myMethod()
{
return 'result';
}
}
// without default, which i'm doing in some instances.
export MyClass {
public myMethod()
{
return 'result';
}
}
When you don't have the default, the import syntax changes.
In a (jest) test if you follow the convention where you do have export default MyClass(){};
then the following works.
const MOCKED_METHOD_RESULT = 'test-result'
jest.mock("MyClass.ts", () => {
// will work and let you check for constructor calls:
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
});
However, if you don't have the default and or are trying to mock other classes etc. then you need to do the following.
Note, that the {get MyClass(){}} is the critical part, i believe you can swap out the jest.fn().mockImplementation() in favour of jest.fn(()=>{})
jest.mock("MyClass.ts", () => ({
get MyClass() {
return jest.fn().mockImplementation(function () {
return {
myMethod: () => {
return MOCKED_METHOD_RESULT;
},
};
});
},
}));
So the issue is the way in which you access the contents of the class your mocking. And the get part allows you to properly define class exports.
I resolved this error by using below code.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: // REPLICATE YOUR API CONSTRUCTOR BEHAVIOUR HERE BY ADDING CLASS
})
If you want to mock complete API class, please check the below snippet.
jest.mock('YOUR_API_PATH', () => ({
__esModule: true,
default: class {
constructor(args) {
this.var1 = args.var1
}
someMethod: jest.fn(() => Promise.resolve())
},
}));

Angular 9 unit test external function referring property inside service gives errors

I have a service to run unit tests as follows. It has two helper functions which use service params as follows.
Here is the service.ts file
export function storeToStorage<T>(key: string, item: T): void {
this.storageService.libLocalStorage( 'set' ,key, item )
}
export function getFromStorage<T>(key: string): T {
return this.storageService.libLocalStorage( 'get' ,key )
}
#Injectable({
providedIn: 'root'
})
export class TableColumnService {
...
constructor(private storageService: LocalStorageService) {
localStorage.removeItem('KEY');
this._initializeColumns();
}
...
}
Here Local storage service is service implemented separately for managing storage of the application. And application is running with out any error.
My implementation for service.spec.ts as follows
describe('TableColumnService', () => {
let service: TableColumnService;
let localStorageService: LocalStorageService;
beforeEach(async (() => {
TestBed.configureTestingModule({
imports: [TestSharedModule],
providers: [LocalStorageService]
});
service = TestBed.inject(TableColumnService);
localStorageService = TestBed.inject(LocalStorageService);
}));
it('should be created', () => {
expect(service).toBeTruthy();
});
});
I am getting following error when running
Chrome Headless 90.0.4430.212 (Mac OS 10.15.7) TableColumnService should be created FAILED
Failed: Cannot read property 'storageService' of undefined
at <Jasmine>
I tried to spy external functions as follows. But giving errors
beforeEach(async (() => {
TestBed.configureTestingModule({
imports: [TestSharedModule],
providers: [LocalStorageService]
});
service = TestBed.inject(TableColumnService);
localStorageService = TestBed.inject(LocalStorageService);
spyOn(storeToStorage, 'storeToStorage').and.returnValue({});
spyOn(getFromStorage, 'getFromStorage').and.returnValue({});
}));
It gives following error on spy functions
Argument of type '{}' is not assignable to parameter of type 'never'
I reckon you're not spying correctly; it should be something like below and yes, you have to make stroage service public in ts file. If you don't want to make it public you can also create a stub for this service in your spec file.
spyOn(service.storageService, 'storeToStorage').and.returnValue({});

JavaScript: Evaluating export code directly from string

How does one run and resolve a JavaScript export module directly from a string?
Here's a concrete example: The module vue-route-generator generates export code that returns a Vue Router configuration object. So when I issue this command:
const routes = require('vue-route-generator').generateRoutes(...);
routes is a string containing code, something like this:
function index() {
return import('/home/jay/Documents/industrial/resources/js/sections/index.vue');
}
function people() {
return import('/home/jay/Documents/industrial/resources/js/sections/people.vue');
}
export default [
{ name: 'index', path: '/', component: index },
{ name: 'people', path: '/people', component: people },
];
How do I run this code and get the object without saving this code to a separate file? I'm using Webpack as my builder but a solution that works for other builders would be appreciated.

Cannot get cookie in vue-router to work. Undefined "this"

The problem is that I cannot get cookie in vue-router. When I try to do this: this.$cookie.get('token'); I get this undefined. Even though I can use this.$cookie in vue components. This is my code snippet:
Vue-Router:
This is a function to check if I get token from cookie (but it doesnt work):
function isLoggedIn (to, from, next) {
console.log(this.$cookie.get('token'));
}
And this is the route and I use that isLoggedIn in it:
{
path: '/',
name: 'login',
component: Login,
beforeEnter: isLoggedIn()
},
Maybe someone knows how to get that cookie ? It it setted up in vue component correctly just don't know how to pass it to vue router.
EDIT
So by using Vue.cookie.get('token'); I get that next is not a function. This is the current function:
function isLoggedIn (to, from, next) {
if(Vue.cookie.get('token')) {
next({name: 'game'});
} else {
next();
}
}
Okay so when I added this function directly into route like this it worked:
{
path: '/',
name: 'login',
component: Login,
beforeEnter: (to, from, next) => {
if(Vue.cookie.get('token')) {
next('/game');
} else {
next;
}
}
},
If you are using the vue-cookie plugin from npm then you will need to use this syntax outside of components:
function isLoggedIn (to, from, next) {
console.log(Vue.cookie.get('token'));
}
With regards to your next is not a function error, this is because you are invoking your function with no arguments, isLoggedIn(), rather than passing the function reference, isLoggedIn, which will be invoked by the router with the appropriate arguments.
Try this instead:
{
path: '/',
name: 'login',
component: Login,
beforeEnter: isLoggedIn // No parentheses here
},
I hope this helps.
If you look at the typings, you'll see that both to and from are Route's. Here's their interface declaration:
export interface Route {
path: string;
name?: string;
hash: string;
query: Dictionary<string | (string | null)[]>;
params: Dictionary<string>;
fullPath: string;
matched: RouteRecord[];
redirectedFrom?: string;
meta?: any;
}
If look at the Definition of BeforeEnter:
beforeEnter?: NavigationGuard;
If you look at the Definition of NavigationGuard:
export type NavigationGuard<V extends Vue = Vue> = (
to: Route,
from: Route,
next: (to?: RawLocation | false | ((vm: V) => any) | void) => void
) => any
So as we can see, the beforeEnter returns a closure that exposes 3 methods to us: (to, from, next).
As we can see the typings of both to and from are just Route Interfaces and next is a function that we pass arguments into, we can safely determine that the scope of this is indeed, undefined.
So you can either define it in the meta of the Route declaration and access it with this.$meta.cookies, or you can import the cookie package directly and use it with cookies.get('token') or you can augment the return type of the beforeEnter method:
beforeEnter: (to, from, next) => isLoggedIn(to, from, next, $cookies)
You still, cannot, use the in component guards as they do not expose this to you because they execute before the component has been created

Export additional interfaces for CommonJS module (Typescript)

I'm trying to use a simple JS library in Typescript/React, but am unable to create a definition file for it. The library is google-kgsearch (https://www.npmjs.com/package/google-kgsearch). It exports a single function in the CommonJS style. I can successfully import and call the function, but can't figure out how to reference the type of the arguments to the result callback.
Here is most of the library code:
function KGSearch (api_key) {
this.search = (opts, callback) => {
....
request({ url: api_url, json: true }, (err, res, data) => {
if (err) callback(err)
callback(null, data.itemListElement)
})
....
return this
}
}
module.exports = (api_key) => {
if (!api_key || typeof api_key !== 'string') {
throw Error(`[kgsearch] missing 'api_key' {string} argument`)
}
return new KGSearch(api_key)
}
And here is my attempt to model it. Most of the interfaces model the results returned by service:
declare module 'google-kgsearch' {
function KGSearch(api: string): KGS.KGS;
export = KGSearch;
namespace KGS {
export interface SearchOptions {
query: string,
types?: Array<string>,
languages?: Array<string>,
limit?: number,
maxDescChars?: number
}
export interface EntitySearchResult {
"#type": string,
result: Result,
resultScore: number
}
export interface Result {
"#id": string,
name: string,
"#type": Array<string>,
image: Image,
detailedDescription: DetailedDescription,
url: string
}
export interface Image {
contentUrl: string,
url: string
}
export interface DetailedDescription {
articleBody: string,
url: string,
license: string
}
export interface KGS {
search: (opts: SearchOptions, callback: (err: string, items: Array<EntitySearchResult>) => void) => KGS.KGS;
}
}
}
My issue is that from another file I am unable to reference the KGS.EntitySearchResult array returned by the search callback. Here is my use of the library:
import KGSearch = require('google-kgsearch');
const kGraph = KGSearch(API_KEY);
interface State {
value: string;
results: Array<KGS.EntitySearchResult>; // <-- Does not work!!
}
class GKGQuery extends React.Component<Props, object> {
state : State;
handleSubmit(event: React.FormEvent<HTMLFormElement>) {
kGraph.search({ query: this.state.value }, (err, items) => { this.setState({results: items}); });
event.preventDefault();
}
....
}
Any suggestions for how to make the result interfaces visible to my calling code without messing up the default export is very greatly appreciated.
The issue here is easily resolved. The problem is that while you have exported KGSearch, you have not exported the namespace KGS that contains the types. There are several ways to go about this, but the one I recommend is to take advantage of Declaration Merging
Your code will change as follows
declare module 'google-kgsearch' {
export = KGSearch;
function KGSearch(api: string): KGSearch.KGS;
namespace KGSearch {
// no changes.
}
}
Then from consuming code
import KGSearch = require('google-kgsearch');
const kGraph = KGSearch(API_KEY);
interface State {
value: string;
results: Array<KGSearch.EntitySearchResult>; // works!!
}
Unfortunately, whenever we introduce an ambient external module declaration, as we have by writing declare module 'google-kgsearch' at global scope, we pollute the global namespace of ambient external modules (that is a mouthful I know). Although it is unlikely to cause a conflict in your specific project for the time being, it means that if someone adds an #types package for google-kgsearch and you have a dependency which in turn depends on this #types package or if google-kgsearch every starts to ship its own typings, we will run into errors.
To resolve this we can use a non-ambient module to declare our custom declarations but this involves a bit more configuration.
Here is how we can go about this
tsconfig.json
{
"compilerOptions": {
"baseUrl": "." // if not already set
"paths": { // if you already have this just add the entry below
"google-kgsearch": [
"custom-declarations/google-kgsearch"
]
}
}
}
custom-declarations/google-kgsearch.d.ts (name does not matter just needs to match paths)
// do not put anything else in this file
// note that there is no `declare module 'x' wrapper`
export = KGSearch;
declare function KGSearch(api: string): KGSearch.KGS;
declare namespace KGSearch {
// ...
}
This encapsulates us from version conflicts and transitive dependency issues by defining it as an external module instead of an ambient external module.
One last thing to seriously consider is sending a pull request to krismuniz/google-kgsearch that adds your typings (the second version) in a file named index.d.ts. Also, if the maintainers do not wish to include them, consider creating an #types/google-kgsearch package by sending a pull request to DefinitelyTyped

Categories

Resources