I am playing with a client side router code from https://github.com/dcode-youtube/single-page-app-vanilla-js repo. I try to change this code in a way that i can reuse, because in the original code he hardcoded the routes in the router function.
I know(i think i know) the main reason why i get the unexpected reserved word error, i just dont know how to solve it.
Router.js
export default class trdsRouter{
constructor(routes){
this.routes = routes;
window.addEventListener("popstate", this.run);
document.body.addEventListener("click", e => {
if (e.target.matches("[trds-router-link]")) {
e.preventDefault();
this.navigateTo(e.target.href);
}
});
}
run = () => {
const potentialMatches = this.routes.map(route => {
return {
route: route,
result: location.pathname.match(pathToRegex(route.path))
};
});
let match = potentialMatches.find(potentialMatch => potentialMatch.result !== null);
if (!match) {
match = {
route: this.routes[0],
result: [location.pathname]
}
}
// THIS LINE IS THE PROBLEM
const view = new match.route.view(getParams(match));
document.querySelector("#app").innerHTML = await view.getHtml();
}
navigateTo = url => {
history.pushState(null, null, url);
this.run();
}
}
const pathToRegex = path => new RegExp("^" + path.replace(/\//g, "\\/").replace(/:\w+/g, "(.+)") + "$");
const getParams = match => {
const values = match.result.slice(1);
const keys = Array.from(match.route.path.matchAll(/:(\w+)/g)).map(result => result[1]);
return Object.fromEntries(keys.map((key, i) => {
return [key, values[i]];
}));
};
where i construct the router:
import trdsRouter from "./router.js";
import Dashboard from "./views/Dashboard.js";
import Posts from "./views/Posts.js";
import PostView from "./views/PostView.js";
import Settings from "./views/Settings.js";
let router = new trdsRouter([
{ path: "/", view: Dashboard },
{ path: "/posts", view: Posts },
{ path: "/posts/:id", view: PostView },
{ path: "/settings", view: Settings }
]);
router.run();
And then there is the abstract view class(i extend this class for the dashboard, posts, etc.):
export default class {
constructor(params) {
this.params = params;
}
setTitle(title) {
document.title = title;
}
async getHtml() {
return "";
}
}
So i think the problem is that in the trdsRouter class it does not yet know that a routes view property is a class( but i pass a class to it when i construct the router), thats why it throws error. how would i solve this? thank you.
Im sorry guys, it was a terrible mistake. The problem wasnt the new keyword highlighted in the routers code but the next line with the 'await' keyword. The routers run function wasnt declared as async so it threw the error. Changed the run function to async and it works. Thank you for your comments.
Related
I am trying to declare a ClientFunction on a Factory page and then call it on a test page. But I am doing something wrong and it doesn’t work. I have two pages one is factory page, second test page.
In test page i have ClientFunction and it works fine. When i trying move to Factory Page it doesn't working.
import {Selector, ClientFunction} from "testcafe";
import videoPage from "../pages/video_player_page";
const videoElement = Selector("#bod-video-player_html5_api");
const currentTime = ClientFunction(() => {
return videoElement().currentTime;
}, {dependencies: {videoElement}});
test
.page(`some video page url`)
("Verify video functionality in LO State ", async (t) => {
let m1 = currentTime();
console.log("m1 = " + m1);
await t
.click(videoPage.fwdButton)
.expect(videoPage.videoPlayer.hasClass("vjs-seeking")).notOk();
let m2 = currentTime();
console.log("m2 = " + m2);
await t.expect(m2).gt(m1+25, "fwd button not working");
});
in factory page i have
import {Selector, t, ClientFunction} from "testcafe";
import {selectors} from "../constants/video_player_page_constants";
import {errorMessages} from "../constants/error_messages/video_player_page_messages";
class VideoPlayerPage {
constructor() {
this.skipAd = Selector(`${selectors.skipAdLocator}`, {timeout: 50000});
this.waiverModal = Selector(`${selectors.warningModalInVideoLocator}`);
this.videoPlayer = Selector(`${selectors.videoPlayerLocator}`);
this.videoPlaying = selectors.videoPlaying;
this.videoPaused = selectors.videoPaused;
this.waiverModalCheckbox = Selector(`${selectors.warningModalCheckbox}`);
this.waiverModalCheckboxCss = selectors.warningModalCheckbox;
this.acceptButton = Selector(`${selectors.warningModalAcceptButton}`);
this.playPauseButton = Selector(`${selectors.playPauseButtonLocator}`);
this.fwdButton = Selector(`${selectors.fwdButtonLocator}`);
this.bwdButton = Selector(`${selectors.bwdButtonLocator}`);
this.videoElement = Selector("#bod-video-player_html5_api");
}
async acceptWaiver() {
await t
.expect(this.waiverModal.exists).ok(errorMessages.warningModalNotExist)
.hover(this.waiverModalCheckboxCss)
.click(this.waiverModalCheckbox)
.click(this.acceptButton);
}
async skipAdd() {
if (await this.skipAd.visible) {
await t.click(this.skipAd);
}
}
} export default new VideoPlayerPage();
How to declare a ClientFunction on the Factory page, thanks for the help.
You can do this:
import { ClientFunction } from 'testcafe';
class VideoPlayerPage {
currentTime () {
return ClientFunction(() => {
const videoEl = document.querySelector('#bod-video-player_html5_api');
return videoEl.currentTime;
})();
}
}
When I trying move to Factory Page it doesn't work.
I guess that's because you export only an instance of VideoPlayerPage, so if you just copy & paster your ClientFunction code, than it's not exported in the first place, so you can't use it in your test file.
EDIT:
If you want to send a selector to ClientFunction as a parameter, this is the way to do it:
import { ClientFunction } from 'testcafe';
class VideoPlayerPage {
constructor () {
this.videoSelector = '#bod-video-player_html5_api';
}
currentTime () {
return ClientFunction(selector => {
const videoEl = document.querySelector(selector);
return videoEl.currentTime;
})(this.videoSelector);
}
}
I have this problem in Next.js. I call the API in getInitialProps and pass the params to my components then it works when I use it in mu project. But when I want to build it gives me error that the params are undefined.
This is how I call my API and pass it to component:
import BlogItem from './blogItem'
import axios from 'axios'
import { withRouter } from 'next/router'
import { APIURL, replaceAll } from '../../../components/config'
const Post = (props) => {
return (
<BlogItem
services ={props.services }
/>
)
}
Post.getInitialProps = async ({ query }) => {
const id = query.id
const urlTitle = encodeURI(query.title)
const services = null;
try {
const response = await axios.get(`${APIURL}getservice`);
services = response.data.response.posts;
} catch (err) {
console.error(err)
}
return {
services
}
}
export default withRouter(Post)
It seems that you defined services as a const that contain null! the whole code seems fine. Try changing const to let:
Post.getInitialProps = async ({ query }) => {
const id = query.id
const urlTitle = encodeURI(query.title)
let services = null; //this line
try {
const response = await axios.get(`${APIURL}getservice`);
services = response.data.response.posts;
} catch (err) {
console.error(err)
}
console.log(services) //you should have the data right here
return {services}
}
Example :
Look at this example. Run it and navigate to /about page :
You need to make sure that your url params are passed into the querystring on the server, In your case, something like this:
server.get('/page/:uid/:id', (req, res) => {
const mergedQuery = Object.assign({}, req.query, req.params)
return app.render(req, res, '/page', mergedQuery);
})
Then your getInitialProps can get them straight from the query, regardless of where it loads.
I have a lot of middleware. Here is one of them. How can I test my middleware with type compliance and with context.state validation on typescript ?
async function internationalizationPlugin(
context: ParameterizedContext<AppState, AppContext>,
next: Next
) {
context.state.i18n = await (i18next as any).createInstance({
lng: context.state.language,
fallbackLng: 'en',
})
await next()
}
a linter will check for type compliances and will be able to customize them more. However, you would just need to make sure that you export the function to your test file and then run a expect(typeof context).to.be(ParameterizedContext<AppState, AppContext>) that's not 100% copy/pasteable code, but, I think that it is on the right track. Also, for testability it could be easier if you created a class out of your middlewares that way importing and testing are done easier.
It's my simple type support solution. I'm not sure if it is suitable for everyone.
import * as httpMocks from 'node-mocks-http'
import * as Koa from 'koa'
export interface MockContext<RequestBody = undefined> extends Koa.Context {
request: Koa.Context['request'] & {
body?: RequestBody
}
}
export const koaMockContext = <
State = Koa.DefaultState,
Context = MockContext,
RequestBody = undefined
>(
requestBody?: RequestBody
) => {
const req = httpMocks.createRequest()
const res = httpMocks.createResponse()
const app = new Koa<State, Context>()
const context = app.createContext(req, res) as MockContext<RequestBody> & Koa.ParameterizedContext<State, Context>
res.statusCode = 404
context.request.body = requestBody
return context
}
And example
import { AppContext, AppState } from './types'
import { koaMockContext } from './utils'
import { internationalizationPlugin } from '../src/internationalizationPlugin'
describe('internationalizationPlugin', () => {
const ctx = koaMockContext<AppState, AppContext>()
it('should not be undefined', async () => {
await internationalizationPlugin(ctx, async () => {})
expect(ctx.state.i18n).not.toBe(undefined)
})
})
I'm trying to check if a user is authenticated. I do this by checking some record in asyncStorage, I have the following code
App.js
let AuthService = require('./app/layouts/AuthService/AuthService.js');
export default class App extends React.Component {
componentDidMount() {
AuthService.getAuthInfo((err, authInfo) => {
this.setState({
checkingAuth: false,
isLoggedIn: authInfo != null
})
});
}
}
Auth.js
'use strict';
let AsyncStorage = require('react-native').AsyncStorage;
let _ = require('lodash');
const authKey = 'auth';
const userKey = 'user';
class AuthService {
getAuthInfo(cb){
AsyncStorage.multiGet([authKey, userKey], (err, val)=> {
if(err){
return cb(err);
}
if(!val){
return cb();
}
let zippedObj = _.zipObject(val);
if(!zippedObj[authKey]){
return cb();
}
let authInfo = {
header: {
Authorization: 'Basic ' + zippedObj[authKey]
},
user: JSON.parse(zippedObj[userKey])
}
return cb(null, authInfo);
});
}
}
module.exports = new AuthService();
In app.js, I'm trying to use this function from Auth.js, but I get no response from the fuction, I get console logs from getAuthInfo before I get into the AsyncStorage function. Im pretty new to react-native and ES6, and I think is a promise or async problem but I cant make it work. In app.js im redering a ActivityIndicator so I dont block the UI with checkingAuth and isLoggedIn.
I tried to use some .then in app.js with no results.
First of all, you return your callback function instead of calling it. Try to call it by removing return like this : cb(null, authInfo);.
EDIT: I have just gone through the process of switching back to browserify and am having the same problem. So no longer a webpack problem. Still need help though
I am in the process of switching from broswerify to webpack. I have created an abstraction for my ajax calls. In that file I have some private variables that I use to set URL and timeout etc. For some reason it shows these variables (and the entire 'closure') as undefined, leading to some weird bugs. This code was working perfectly fine with browserify.
This is my webpack.config.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
devtool: 'source-map',
entry: path.resolve(__dirname, 'src', 'client', 'index.js'),
output: {
path: path.resolve(__dirname, 'public'),
publicPath: 'localhost:3002',
filename: 'bundle.js',
},
module: {
loaders: [
{
test: /\.js$/,
loader: 'babel',
exclude: /node_modules/,
},
{
test: /\.(png|jpg)$/,
loader: 'url-loader',
},
],
},
plugins: [
new webpack.DefinePlugin({
__API_URL__: JSON.stringify('http://localhost:3002/api'),
}),
],
};
This is my api wrapper api.js
import request from 'superagent';
import store from './store';
import { system, account } from '../core/actions';
const API_URL = __API_URL__;
const TIMEOUT = 10000;
const _pendingRequests = {};
function getJwt() {
/**
* This retrieves the JSON Web Token from local or session storage
* We simply try both so that we don't have to subscribe to the store
* and make sure some flag is constantly updated. The reducer that handles
* the successful login will place the token in the proper place.
*/
let token = localStorage.getItem('JWT_TOKEN');
if (token) {
return 'Bearer ' + token;
}
token = sessionStorage.getItem('JWT_TOKEN');
if (token) {
return 'Bearer ' + token;
}
return null;
}
function addRequest(key, pendingRequest) {
_pendingRequests[key] = pendingRequest;
}
function abortPendingRequests(key) {
if (_pendingRequests.hasOwnProperty(key)) {
_pendingRequests[key]._callback = () => {
};
_pendingRequests[key].abort();
_pendingRequests[key] = null;
}
}
function digest(resolve, reject) {
return function consume(err, res) {
if (err && err.timeout === TIMEOUT) {
return store.dispatch(system.apiTimeout());
} else if (res.status === 401) {
return store.dispatch(account.logout());
} else if (!res.ok) {
return reject(res);
} else {
if (err) {
return reject(err);
} else {
return resolve(res.body);
}
}
};
}
export function get(actionType, resource) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.get(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
// addRequest(actionType, requested);
requested.end(digest(resolve, reject));
});
}
export function post(actionType, resource, data) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.post(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
if (data) {
requested.send(data);
}
// addRequest(actionType, requested);
requested.end(digest(resolve, reject));
});
}
export function put(actionType, resource, data) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.put(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
if (data) {
requested.send(data);
}
requested.end(digest(resolve, reject));
});
}
export function del(actionType, resource) {
// abortPendingRequests(actionType);
return new Promise((resolve, reject) => {
const jwt = getJwt();
const url = `${API_URL}${resource}`;
const requested = request
.del(url)
.timeout(TIMEOUT);
if (jwt) {
requested.set('Authorization', jwt);
}
// addRequest(actionType, requested);
requested.end(digest(resolve, reject));
});
}
There are some weird comments that are the result of trying to debug the problem. But basically, if I set a breakpoint at const _pendingRequests = {}; it shows API_URL and TIMEOUT as being set properly. But if I set a breakpoint at const url =${API_URL}${resource}; in export function get it shows them as undefined as I will show with screenshots.
One thing I am just noticing is that it is breaking on the child scope prior to breaking on the parent scope. I am guessing that has something to do with it, but I am not sure how to change this behavior. I work in node so I have written this like I would write it for the server.
This is the file where I am importing api.js
import * as api from '../../core/api';
import { endpoints } from '../../constants';
export const FETCH_LOOKUPS = 'FETCH_LOOKUPS';
export const FETCH_LOOKUPS_SUCCESS = 'FETCH_LOOKUPS_SUCCESS';
export function fetchLookupsSuccess(lookups) {
return {
type: FETCH_LOOKUPS_SUCCESS,
lookups,
};
}
export function asyncFetchLookups() {
return dispatch => {
return api.get(FETCH_LOOKUPS, endpoints.LOOKUP)
.then(lookups => dispatch(fetchLookupsSuccess(lookups)));
};
}
export const FETCH_LANG = 'FETCH_LANG';
export const FETCH_LANG_SUCCESS = 'FETCH_LANG_SUCCESS';
export function fetchLangSuccess(language) {
return {
type: FETCH_LANG_SUCCESS,
language,
};
}
export function asyncFetchLang() {
return dispatch => {
return api.get(FETCH_LANG, endpoints.LANGUAGE)
.then(language => dispatch(fetchLangSuccess(language)));
};
}
Started digging into the transpiled code and found this
function(module, exports, __webpack_require__) {
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.TIMEOUT = exports.API_URL = undefined;
exports.get = get;
exports.post = post;
exports.put = put;
exports.del = del;
var _superagent = __webpack_require__(427);
var _superagent2 = _interopRequireDefault(_superagent);
var _store = __webpack_require__(430);
var _store2 = _interopRequireDefault(_store);
var _actions = __webpack_require__(444);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var API_URL = exports.API_URL = ("http://localhost:3002/api"); /**
* This file serves as a wrapper for any ajax requests that need to be made
* - contains a generic call for PUT, POST, GET and DELETE request
* - always attempts to append a JSON Web Token if present
* - keeps track of all pending requests and aborts identical requests
*/
var TIMEOUT = exports.TIMEOUT = 10000;
As you can see it initially sets TIMEOUT and API_URL as undefined. It then exports get, post etc and then sets TIMEOUT and API_URL but that is after the exported get is already being accessed. Not sure why it sets them to undefined or how to fix this behavior.
According to how you require (probably import) the get function babel may transpile first the get function and hand it over to node's require which then evals it.
You then don't have API_URL transpiled yet. This looks like an edge case.
Instead of using the ES6 export, just for this file, use module.exports and use node's require to import it to dismiss that kind of bug.
If this works, try importing instead of requireing (and the way around exporting instead of using module.exports) to narrow the bug.
Note: This is more a hint/workaround than a solution. You may provide the file from where you make the require, this might be useful for other people to answer you.
The problem had to do with the function being called prior the javascript finishing parsing I guess. (Still not sure why) The workaround I found was to attach my initialization function to the window object and call it in an IIFE at the body of my HTML.