Topic based pub/sub for JS - javascript

I want to be able to have a pubsub mechanism similar to found in jQuery's custom events and PubSubJS (http://github.com/mroderick/PubSubJS).
The problem is that each one of these pubsub libraries does an exact match on the subject. IO want to be able to publish a subject like:
"Order/Sent/1234"
And have a listen subscribe to either:
"Order/Sent/1234"
"Order/Sent/*"
"Order/*/1234"
Does anyone know of anything like this for JS?

Just modify the one you like. Fork it on github, go open pubsub.js and do something like:
var deliverMessage = function(){
var subscribers = [];
for(var n in messages){
if( new RegExp('^' + n + '$').test( message ) ){
subscribers = subscribers.concat( messages[n] );
}
...
}
}
You'll probably need to modify that a bit, do something like replace all *'s with .* or [^\/]* before converting to a regex, etc...

// Paytm's turbo churged Publish - Subscribe Library
export const PaytmConnect = (() => {
const topics = {};
return {
subscribe: (topic, listener) => {
// Create the topic's object if not yet created
if (!topics.hasOwnProperty(topic)) topics[topic] = [];
// Add the listener to queue
const index = topics[topic].push(listener) - 1;
// Provide handle back for removal of topic
return {
remove: () => {
delete topics[topic][index];
}
};
},
publish: (topic, info) => {
// If the topic doesn't exist, or there's no listeners in queue, just leave
if (!topics.hasOwnProperty(topic)) return;
const allProperty = Object.getOwnPropertyNames(topic);
allProperty.forEach((property) => {
if (property.match(topic)) {
// Cycle through topics queue, fire!
topics[topic].forEach((item) => {
item(info !== undefined ? info : {});
});
}
});
}
};
})();
You will have to modify the code if (property.match(topic)) { a bit to satisfy your requirement.

Related

How do I wait until a cookie is set?

I am writing the acceptance tests for my application's login feature. At some point, I want to double-check the cookie's expiry time.
Upon clicking on the "Login" button, a graphql query is sent to my server which responds with a Jwt. Upon reception of the jwt, the application sets the cookie with
document.cookie = ...
In my Cypress test, I check the token in the following way:
Then("sa session s'ouvre pour {SessionDurationType}", expectedDuration => {
cy.get('#graphql').then(() => {
cy.wait(1000)
cy.getCookie('token').then(cookie => {
const tokenDuration = getTokenDuration(cookie.value)
expect(tokenDuration.asSeconds()).to.equal(expectedDuration.asSeconds())
})
})
})
With cy.get('#graphql'), I am waiting for the graphql query to return a response. The alias is defined like this:
cy.stub(win, 'fetch', fetch).as('graphql')
Upon reception, the application sets the cookie.
My problem is that I am not fond of the following call:
cy.wait(1000)
Without that call, I always get an undefined cookie.
Is there a way to get that cookie within some time that might be much less than 1000 ms? I tried many things without success...
You must write a recursive promise function, try the following
function checkCookie() {
// cy.getCookie returns a thenebale
return cy.getCookie('token').then(cookie => {
const tokenDuration = getTokenDuration(cookie.value);
// it checks the seconds right now, without unnecessary waitings
if(tokenDuration.asSeconds() !== expectedDuration.asSeconds()) {
// waits for a fixed milliseconds amount
cy.wait(100);
// returns the same function recursively, the next `.then()` will be the checkCookie function itself
return checkCookie();
}
// only when the condition passes returns a resolving promise
return Promise.resolve(tokenDuration.asSeconds());
})
}
Then("sa session s'ouvre pour {SessionDurationType}", expectedDuration => {
cy.get('#graphql').then(() => {
checkCookie()
.then(seconds => {
expect(seconds).to.equal(expectedDuration.asSeconds())
})
})
})
Note that the function must be improved because
I didn't parametrize the expectedDuration etc. (it's out of the scope of showing you how to do that)
it waits forever without a loop counter check
But it works (I checked in another context before replying to you) and if you have some more troubles please share a "working" GitHub repo so I can clone and check it with your own solution.
Let me know if it isn't enough clear 😉
UPDATE
We (me and Tommaso) have written a plugin to help you with this kind of checks, its name is cypress-wait-until.
Please thank the Open Source Saturday community for that, we developed it during one of them Saturdays 😊
I dont like the timeout in this i have to say for dom changes. I have come up with this solution based on #NoriSte Answer together with DomMutation Observers.
getFileUploadItem().get(".upload-item--state i")
.should("have.class", "ngx-fileupload-icon--start")
.then(item => {
const iconEl = item.get(0);
const states: string[] = [];
return new Promise((resolve, reject) => {
const observer = new MutationObserver((mutations: MutationRecord[]) => {
const mutationEl = mutations[0].target as HTMLElement;
const className = mutationEl.getAttribute("class");
states.push(className);
if (className === "ngx-fileupload-icon--uploaded") {
resolve(states);
}
});
observer.observe(iconEl, {
subtree: true,
attributes: true,
attributeFilter: ["class"]
});
});
})
.then((value) => expect(value).to.deep.equal(
["ngx-fileupload-icon--progress", "ngx-fileupload-icon--uploaded"])
);
Based on #NoriSte's answer, I came up with the following working code:
function awaitNonNullToken(elapsedTimeInMs = 0) {
let timeDeltaInMs = 10
if (elapsedTimeInMs > Cypress.env('timeoutInMs')) {
return Promise.reject(new Error('Awaiting token timeout'))
}
return getTokenCookie().then(cookie => {
if (cookie === null) {
cy.wait(timeDeltaInMs)
elapsedTimeInMs += timeDeltaInMs
return awaitNonNullToken(elapsedTimeInMs)
}
return Promise.resolve(cookie.value)
})
}
I transformed that into an ES6 class that I find a bit more elegant:
class TokenHandler {
constructor () {
this.TIME_DELTA_IN_MS = Cypress.env('timeDeltaInMs')
this.TIMEOUT_IN_MS = Cypress.env('timeoutInMs')
this.elapsedTimeInMs = 0
}
getToken () {
if (this.elapsedTimeInMs > this.TIMEOUT_IN_MS) {
return Promise.reject(new Error('Awaiting token timeout'))
}
return getTokenCookie().then(cookie => {
if (cookie === null) {
cy.wait(this.TIME_DELTA_IN_MS)
this.elapsedTimeInMs += this.TIME_DELTA_IN_MS
return this.getToken()
}
return Promise.resolve(cookie.value)
})
}
}
and reworked my step like this:
cy.get('#graphql').then(() => {
const handler = new TokenHandler
handler.getToken().then(token => {
const tokenDuration = getTokenDuration(token)
expect(tokenDuration.asSeconds()).to.equal(expectedDuration.asSeconds())
})
})
This is working perfectly, thanks.

How to abstract common function out from the test file in Cypress

I just moved from TestCafe to Cypress and couldn't find a solution to abstract a common frequently used method. In this example below cy.document().then(doc).. is used twice, however I believe that these types of function must be abstracted to reusable function.
it('Test the input text field and submit button list the basket items', () => {
const allNameBeforeInput = []
const allNameAfterInput = []
cy.document().then((doc) => {
const elements = doc.querySelector('#items').querySelectorAll('.row-style > :nth-child(1)')
for (let i = 0; i <= elements.length - 1; i++) {
const basketName = elements[i].textContent
if (basketName && basketName !== '') {
allNameBeforeInput.push(`${basketName}`)
}
console.log(allNameBeforeInput.length) //this gives 0
}
})
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get('#items').children('.row-style').children('.list-item')
.contains('Suraj')
cy.document().then((doc) => {
const elements = doc.querySelector('#items').querySelectorAll('.row-style > :nth-child(1)')
for (let i = 0; i <= elements.length - 1; i++) {
const basketName = elements[i].textContent
if (basketName && basketName !== '') {
allNameAfterInput.push(`${basketName}`)
}
}
console.log(allNameAfterInput.length) //this gives 3
expect(allNameBeforeInput.length).equal(0)
expect(allNameAfterInput.length).equal(3)
expect(allNameBeforeInput.length).is.lt(allNameAfterInput.length)
})
})
This is what I want to accomplished with class Basket:
getAllBasketName() {
cy.document().then((doc) => {
const allName = []
const elements = doc.querySelector('#items').querySelectorAll('.row-style > :nth-child(1)')
for (let i = 0; i <= elements.length - 1; i++) {
const basketName = elements[i].textContent
if (basketName && basketName !== '') {
allName.push(`${basketName}`)
}
}
return allName
})
}
Now I should be able to use
const getAllBasketNamesBefore = basket.getAllBasketName()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
cy.get(basket.itemInputField)
.type('Suraj')
cy.get(basket.submitInputButtonField)
.click()
const getAllBasketNamesAfter = basket.getAllBasketName()
{Assertion goes here}
This is not working because of async/await is not handled so the value of before and after are alway 0. Any clue or help will be appreciated.
The method you are using is not recommended by cypress and is considered an anti-pattern. https://docs.cypress.io/guides/references/best-practices.html#Assigning-Return-Values
Cypress recommends that you add custom commands. https://docs.cypress.io/api/cypress-api/custom-commands.html#Syntax
In the initially created folder structure, the commands.js file can be found under the support folder. Here you are able to create a command that wraps up the logic you wish to reuse. Based on the console.log portion of your code I assume this is run on the command line. There are custom commands for the console as well as for use in the UI.
for that portion you may have to add this custom command
// not a super useful custom command
// but demonstrates how subject is passed
// and how the arguments are shifted
Cypress.Commands.add('console', {
prevSubject: true
}, (subject, method) => {
// the previous subject is automatically received
// and the commands arguments are shifted
// allow us to change the console method used
method = method || 'log'
// log the subject to the console
console[method]('The subject is', subject)
// whatever we return becomes the new subject
//
// we don't want to change the subject so
// we return whatever was passed in
return subject
})
For the other functionality creating commands is pretty simple, the basic pattern is:
Cypress.Commands.add(name, callbackFn)
so you can potentially create something like
Cypress.Commands.add(allNameBeforeInput, (options, options) => {
//custom logic goes here
})
Then you can use it by calling cy.allNameBeforeInput(options, options).
For instance, I was struggling with login and all my tests had login functions to log in through the UI but I wanted to start my tests on the correct page instead of the the log in page. I added this to the command.js file in the support folder:
Cypress.Commands.add('login',(username="notsharingmyusernam#stackexchange.com",
password="somesecurepasswordshhh") => {
cy.request({
method: "POST",
url: "/api/public/login",
body: `{:person/email "${username}", :person/password "${password}"}`,
headers: {
"Accept": "application/edn",
"Content-Type": "application/edn"
}
})
})
And now I can add the cy.login and a beforeEach function at the beginning of my tests. The before each to make the request to the server and wait for the request for login and the cy.login custom command to ensure that I can use that bundled up logic with just one cy command.
describe('Test suite for page traverse', () => {
beforeEach(() => {
cy.server()
cy.route("POST","/api/graphql").as("graphql")
Cypress.Cookies.preserveOnce("company_jwt_qa")
})
it('traverses all subnav items', () => {
cy.login()
cy.visit('/index.html')
cy.wait("#graphql")
cy.get('[data-tag-component="subnav-group"]')
cy.get('[data-tag-component="subnav-title"]').eq(1).click()
})
})

How to create/postmessage to web workers in an array

Question is simple:
Example:
For (iterate based on amount of cores){
Let worker = workers[I]
Worker.postmessage
}
End of example .
Disclaimer: This example only shows what is expected of the end result and is in no means in what is considered "working condition" . Also note that the method used above does not return a worker for "workers[iterator]" instead just undefined.
Objective: Create working methods:
1: make array of unknown amount of workers(based on cores).
2: once that array is built, post a message to each worker and have a returned result(other than undefined).
Note: I do have a hypothesis of why it does not work:
1: web workers are created and are only accessable through the event that created them and its only acception is the onmessage "event" handler .
in defiance of my hypothesis there is such things that would say neigh to what is written above for example , like thread.js that allows for thread pooling and other procedures.
This is the main reason of why I ask , because I do know it is possible but would like a simple answer.
Thanks for your time .
Here is an example:
function createWorker (workerScript) {
const blob = new Blob([`(${workerScript})(self)`], {type: 'application/javascript'});
return new Worker(URL.createObjectURL(blob));
};
function workerCode (self) {
self.onmessage = function (message) {
postMessage(`Data from worker: ${message.data}`);
};
};
// assuming that you will send only one message to the worker,
// and that the worker will produce only one message too.
function workerPromise (worker, message) {
const promise = new Promise((resolve, reject) => {
worker.onmessage = resolve;
}).then(message => message.data);
worker.postMessage(message);
return promise;
}
(async () => {
const workers = [];
for (let i = 0; i < navigator.hardwareConcurrency; i++) {
workers.push(createWorker(workerCode));
}
const results = await Promise.all(
workers.map((w, index) => workerPromise(w, `worker ${index}`))
);
console.log(results);
})();
Start with the "Examples" section under: https://developer.mozilla.org/en-US/docs/Web/API/NavigatorConcurrentHardware/hardwareConcurrency
Modified example:
// in Main thread
const numberOfCPUCores = window.navigator.hardwareConcurrency;
const workerList = [];
const cpuWorkerMessageHandler = event => {
// process message from a worker by accessing: event.data
}
for (let i = 0; i < numberOfCPUCores; i++) {
const newWorker = new Worker('cpuworker.js');
newWorker.addEventListener("message", cpuWorkerMessageHandler);
workerList.push(newWorker);
}
// then, when done with all processing, terminate all workers
workerList.forEach(w => w.terminate());

How to buffer an observable based on buffer content in rxjs?

I would like to flush a buffered observable based on the content of the buffer, but how to accomplish this? A simplified example of what I want to do:
observable.buffer(() => {
// Filter based on the buffer content.
// Assuming an observable here because buffer()
// needs to return an observable.
return buffer.filter(...);
})
Here is more specifically what I am trying to do with key events (bin here):
const handledKeySequences = ['1|2']
// Mock for document keydown event
keyCodes = Rx.Observable.from([1,2,3,4])
keyCodes
.buffer(() => {
/*
The following doesn't work because it needs to be an
observable, but how to observe what is in the buffer?
Also would like to not duplicate the join and includes
if possible
return function (buffer) {
return handledKeySequences.includes(buffer.join('|'));
};
*/
// Returning an empty subject to flush the buffer
// every time to prevent an error, but this is not
// what I want.
return new Rx.Subject();
})
.map((buffer) => {
return buffer.join('|')
})
.filter((sequenceId) => {
return handledKeySequences.includes(sequenceId);
})
.subscribe((sequenceId) => {
// Expecting to be called once with 1|2
console.log("HANDLING", sequenceId)
})
I feel like my approach is wrong, but I can't figure out what the right approach would be. I've tried using scan, but that scans all the events in the observable, which is not what I want.
Thanks for any help!
This should be doable with bufferWithCount:
const handledKeySequences = ['1|2']
// Mock for document keydown event
keyCodes = Rx.Observable.from([0,1,2,3,4]);
const buffer$ = keyCodes
.bufferWithCount(2, 1) // first param = longest possible sequence, second param = param1 - 1
.do(console.log)
.map((buffer) => {
return buffer.join('|')
})
.filter((sequenceId) => {
return handledKeySequences.includes(sequenceId);
});
buffer$.subscribe((sequenceId) => {
console.log("HANDLING", sequenceId)
});
See live here.
Also have a look at this question.
It seems that this functionality is not currently available in Rxjs, so as suggested by #olsn I wrote a custom operator that works by passing a function to tell when to flush the buffer:
(function() {
// Buffer with function support.
function bufferWithContent(bufferFn) {
let buffer = [];
return this.flatMap(item => {
buffer.push(item);
if (bufferFn(buffer)) {
// Flush buffer and return mapped.
let result = Rx.Observable.just(buffer);
buffer = [];
} else {
// Return empty and retain buffer.
let result = Rx.Observable.empty();
}
return result;
});
}
Rx.Observable.prototype.bufferWithContent = bufferWithContent;
})();
I also opened an issue here that proposes adding this functionality.

Combine Observables A and B: Should only trigger if A has data; take from B if data or default

I have an application communicating with a device via serial port. Every sent command is answered by a data event containing the status/answer. Basically there are commands changing the device and a command which just returns the status. Every time the last command has been answered (so upon receiving data) the app should send the next command or as a default query the status. I'm trying to model this with rxjs.
My idea here is that there is a command observable and a data observable derived from the data event. These two should be combined in such a way, that the resulting observable only emits values, when there is data and combine it with a command or the default command (request status), if no command came down the command stream.
data: ---------d---d-----d---------d------d-------
command: --c1---c2----------------------c3-----------
______________________________________________________
combined ---------c1--c2----dc--------dc-----c3
dc is the default command. Also no commands should be lost.
Currently I have an implementation with a anonymous subject, implementing the observable and observer myself. Collecting commands from the command stream in an array, subscribing to the data event, publish the data by hand with onNext and sending the next command from the array or the default. This works, but I have the feeling this could be expressed more elegantly with rxjs.
One approach was to have a separate default_command stream, repeating the default command every 100ms. This was merged with the command stream and then zipped with the data stream. The problem here was the merged command stream, because it piled up default commands, but the default command should only apply, if there is no other command.
Only thing I can think of is to:
subscribe to the command stream and queue the results (in an array)
Apply a mapping operation to the data stream which will pull from the queue (or use default if the queue is empty).
We can wrap this up into a generic observable operator. I'm bad with names, so I'll call it zipWithDefault:
Rx.Observable.prototype.zipWithDefault = function(bs, defaultB, selector) {
var source = this;
return Rx.Observable.create(function(observer) {
var sourceSubscription = new Rx.SingleAssignmentDisposable(),
bSubscription = new Rx.SingleAssignmentDisposable(),
subscriptions = new Rx.CompositeDisposable(sourceSubscription, bSubscription),
bQueue = [],
mappedSource = source.map(function(value) {
return selector(value, bQueue.length ? bQueue.shift() : defaultB);
});
bSubscription.setDisposable(bs.subscribe(
function(b) {
bQueue.push(b);
},
observer.onError.bind(observer)));
sourceSubscription.setDisposable(mappedSource.subscribe(observer));
return subscriptions;
});
};
And use it like so:
combined = dataStream
.zipWithDefault(commandStream, defaultCommand, function (data, command) {
return command;
});
I think the sample operator would be your best bet. Unfortunately, it does not come with a built in default value, so you would have to roll your own from the existing operator:
Rx.Observable.prototype.sampleWithDefault = function(sampler, defaultValue){
var source = this;
return new Rx.AnonymousObservable(function (observer) {
var atEnd, value, hasValue;
function sampleSubscribe() {
observer.onNext(hasValue ? value : defaultValue);
hasValue = false;
}
function sampleComplete() {
atEnd && observer.onCompleted();
}
return new Rx.CompositeDisposable(
source.subscribe(function (newValue) {
hasValue = true;
value = newValue;
}, observer.onError.bind(observer), function () {
atEnd = true;
}),
sampler.subscribe(sampleSubscribe, observer.onError.bind(observer), sampleComplete)
);
}, source);
}
You can achieve the queuing behavior using the controlled operator. Thus your final data chain would look like so:
var commands = getCommandSource().controlled();
var pipeline = commands
.sampleWithDefault(data, defaultCommand)
.tap(function() { commands.request(1); });
Here is a full example:
Rx.Observable.prototype.sampleWithDefault = function(sampler, defaultValue) {
var source = this;
return new Rx.AnonymousObservable(function(observer) {
var atEnd, value, hasValue;
function sampleSubscribe() {
observer.onNext(hasValue ? value : defaultValue);
hasValue = false;
}
function sampleComplete() {
atEnd && observer.onCompleted();
}
return new Rx.CompositeDisposable(
source.subscribe(function(newValue) {
hasValue = true;
value = newValue;
}, observer.onError.bind(observer), function() {
atEnd = true;
}),
sampler.subscribe(sampleSubscribe, observer.onError.bind(observer), sampleComplete)
);
}, source);
}
var scheduler = new Rx.TestScheduler();
var onNext = Rx.ReactiveTest.onNext;
var onCompleted = Rx.ReactiveTest.onCompleted;
var data = scheduler.createHotObservable(onNext(210, 18),
onNext(220, 17),
onNext(230, 16),
onNext(250, 15),
onCompleted(1000));
var commands = scheduler.createHotObservable(onNext(205, 'a'),
onNext(210, 'b'),
onNext(240, 'c'),
onNext(400, 'd'),
onCompleted(800))
.controlled(true, scheduler);
var pipeline = commands
.sampleWithDefault(data, 'default')
.tap(function() {
commands.request(1);
});
var output = document.getElementById("output");
pipeline.subscribe(function(x) {
var li = document.createElement("li");
var text = document.createTextNode(x);
li.appendChild(text);
output.appendChild(li);
});
commands.request(1);
scheduler.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.all.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/2.5.2/rx.testing.js"></script>
<div>
<ul id="output" />
</div>
This can be solved by using the scan function. In the accumulated value the commands are stored for which no data response has been received yet.
var result = Rx.Observable
.merge(data, command)
.scan(function (acc, x) {
if (x === 'd') {
acc.result = acc.commands.length > 0 ? acc.commands.shift() : 'dc';
} else {
acc.result = '';
acc.commands.push(x);
}
return acc;
}, {result: '', commands: []})
.map(function (x) {
return x.result;
})
.filter(function (x) {
return x !== '';
});
Please find a complete more detail here: http://jsbin.com/tubade/edit?html,js,console

Categories