Skip to content

Commit

Permalink
Merge 4b45762 into a1af317
Browse files Browse the repository at this point in the history
  • Loading branch information
stwa committed Jun 18, 2019
2 parents a1af317 + 4b45762 commit e939610
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 0 deletions.
78 changes: 78 additions & 0 deletions src/batteries.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { effector } from "./effect";
import { trigger } from "./event";

/**
* Effect that triggers an event with args.
*
* @example
*
* {
* trigger: ["event-id", "arg1", "arg2"]
* }
*/
effector("trigger", ([eventId, ...args]) => trigger(eventId, ...args));

const STATUS_OK = 200;
const STATUS_CREATED = 201;
const STATUS_ACCEPTED = 202;
const STATUS_NO_CONTENT = 204;
const STATUS_PARTIAL_CONTENT = 206;
const STATUS_NOT_MODIFIED = 304;

/**
* Effect that performs an XML HTTP request to interact with a server.
*
* @example
*
* {
* xhr: {
* url: "/endpoint",
* method: "GET",
* onSuccess: ["success"]
* }
* }
*/
effector(
"xhr",
({
url,
method = "GET",
responseType = "",
timeout = 3000,
headers = {},
data,
onSuccess,
onError,
}) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
xhr.responseType = responseType;
xhr.timeout = timeout;
for (let name in headers) {
xhr.setRequestHeader(name, headers[name]);
}
xhr.onload = xhr.onerror = function() {
switch (this.status) {
case STATUS_OK:
case STATUS_CREATED:
case STATUS_ACCEPTED:
case STATUS_NO_CONTENT:
case STATUS_PARTIAL_CONTENT:
case STATUS_NOT_MODIFIED:
if (onSuccess) {
trigger(...onSuccess, this.response);
}
break;
default:
if (onError) {
trigger(...onError, {
status: this.status,
statusText: this.statusText,
response: this.response,
});
}
}
};
xhr.send(data);
},
);
119 changes: 119 additions & 0 deletions src/batteries.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import "./batteries";
import { effect } from "./effect";
import { trigger } from "./event";

jest.mock("./event");

let xhrs = [];
beforeEach(() => {
xhrs = [];
trigger.mockClear();
});

window.XMLHttpRequest = function() {
let xhr = {
open: jest.fn(),
send: jest.fn(),
setRequestHeader: jest.fn(),
};
// remember created xhr requests
xhrs = [...xhrs, xhr];
return xhr;
};

describe("trigger effect", () => {
it("should trigger the described event", () => {
effect("trigger", ["foo", "bar", "baz"]);
expect(trigger).toHaveBeenCalledTimes(1);
expect(trigger).toHaveBeenCalledWith("foo", "bar", "baz");
});
});

describe("xhr effect", () => {
it("has sane defaults", () => {
effect("xhr", {});

const xhr = xhrs[0];
expect(xhr.open.mock.calls[0]).toEqual(["GET", undefined]);
expect(xhr.send.mock.calls[0]).toEqual([undefined]);
expect(xhr.responseType).toBe("");
expect(xhr.timeout).toBe(3000);
expect(xhr.setRequestHeader.mock.calls.length).toBe(0);
});
it("overrides defaults", () => {
effect("xhr", {
url: "/foo",
method: "POST",
responseType: "json",
timeout: 42,
headers: { Foo: "my-foo", Bar: "my-bar" },
data: "some-data",
});

const xhr = xhrs[0];
expect(xhr.open.mock.calls[0]).toEqual(["POST", "/foo"]);
expect(xhr.send.mock.calls[0]).toEqual(["some-data"]);
expect(xhr.responseType).toBe("json");
expect(xhr.timeout).toBe(42);
expect(xhr.setRequestHeader.mock.calls[0]).toEqual(["Foo", "my-foo"]);
expect(xhr.setRequestHeader.mock.calls[1]).toEqual(["Bar", "my-bar"]);
});
it("triggers an event on success", () => {
effect("xhr", {
onSuccess: ["success", "foo"],
onError: ["error", "foo"],
});

const xhr = xhrs[0];
[200, 201, 202, 204, 206, 304].forEach(status => {
trigger.mockClear();
xhr.onload.call({
status: status,
statusText: "${status}",
response: "response",
});
expect(trigger).toHaveBeenCalledTimes(1);
expect(trigger).toHaveBeenCalledWith("success", "foo", "response");
});
});
it("triggers an event on error", () => {
effect("xhr", {
onSuccess: ["success", "foo"],
onError: ["error", "foo"],
});

const xhr = xhrs[0];
const response = {
status: 500,
statusText: "Server error",
response: "response",
};
xhr.onload.call(response);
expect(trigger).toHaveBeenCalledTimes(1);
expect(trigger).toHaveBeenCalledWith("error", "foo", response);
});
it("ignores success if onSuccess is unset", () => {
effect("xhr", {});

const xhr = xhrs[0];
const response = {
status: 200,
statusText: "OK",
response: "response",
};
xhr.onload.call(response);
expect(trigger).not.toHaveBeenCalled();
});
it("ignores errors if onError is unset", () => {
effect("xhr", {});

const xhr = xhrs[0];
const response = {
status: 500,
statusText: "Server error",
response: "response",
};
xhr.onload.call(response);
expect(trigger).not.toHaveBeenCalled();
});
});
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ export { mount } from "./dom";
export { effect, effector } from "./effect";
export { handler, rawHandler, trigger, triggerImmediately } from "./event";
export { signal, signalFn } from "./signal";

import "./batteries";

0 comments on commit e939610

Please sign in to comment.