зеркало из https://github.com/github/remote-form.git
Merge pull request #18 from github/typescript
Convert project to Typescript
This commit is contained in:
Коммит
75043852f4
18
.babelrc
18
.babelrc
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"env": {
|
||||
"esm": {
|
||||
"presets": ["github"]
|
||||
},
|
||||
"umd": {
|
||||
"plugins": [
|
||||
["@babel/plugin-transform-modules-umd", {
|
||||
"globals": {
|
||||
"selector-set": "SelectorSet"
|
||||
}
|
||||
}]
|
||||
],
|
||||
"moduleId": "remoteForm",
|
||||
"presets": ["github"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +1,20 @@
|
|||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"extends": [
|
||||
"plugin:github/es6",
|
||||
"plugin:github/browser",
|
||||
"plugin:github/flow"
|
||||
"plugin:github/typescript"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-non-null-assertion": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "test/**/*.js",
|
||||
"parser": "espree",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2020
|
||||
},
|
||||
"rules": {
|
||||
"flowtype/require-valid-file-annotation": "off",
|
||||
"github/unescaped-html-literal": "off",
|
||||
|
|
11
.flowconfig
11
.flowconfig
|
@ -1,11 +0,0 @@
|
|||
[ignore]
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
|
||||
[lints]
|
||||
|
||||
[options]
|
||||
|
||||
[strict]
|
|
@ -0,0 +1 @@
|
|||
* @github/web-systems-reviewers
|
|
@ -1,32 +0,0 @@
|
|||
type Kicker = {
|
||||
text: () => Promise<SimpleResponse>,
|
||||
json: () => Promise<SimpleResponse>,
|
||||
html: () => Promise<SimpleResponse>
|
||||
}
|
||||
|
||||
type SimpleRequest = {
|
||||
method: string,
|
||||
url: string,
|
||||
body: FormData | null,
|
||||
headers: Headers
|
||||
}
|
||||
|
||||
export type SimpleResponse = {
|
||||
url: string,
|
||||
status: number,
|
||||
statusText: string,
|
||||
headers: Headers,
|
||||
text: string,
|
||||
json: {[key: string]: any},
|
||||
html: DocumentFragment
|
||||
}
|
||||
|
||||
export type ErrorWithResponse = {
|
||||
response: SimpleResponse
|
||||
}
|
||||
|
||||
export type RemoteFormHandler = (form: HTMLFormElement, kicker: Kicker, req: SimpleRequest) => void | Promise<void>;
|
||||
export function afterRemote(fn: (form: HTMLFormElement) => void | Promise<void>): void;
|
||||
export function beforeRemote(fn: (form: HTMLFormElement) => void | Promise<void>): void;
|
||||
export function remoteForm(selector: string, fn: RemoteFormHandler): void;
|
||||
export function remoteUninstall(selector: string, fn: RemoteFormHandler): void;
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
32
package.json
32
package.json
|
@ -4,24 +4,22 @@
|
|||
"description": "Decorator that will submit a form over AJAX",
|
||||
"repository": "github/remote-form",
|
||||
"files": [
|
||||
"dist",
|
||||
"index.d.ts"
|
||||
"dist"
|
||||
],
|
||||
"main": "dist/index.umd.js",
|
||||
"module": "dist/index.esm.js",
|
||||
"types:": "index.d.ts",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.js",
|
||||
"types:": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "github-lint",
|
||||
"prebuild": "npm run clean && npm run lint && mkdir dist",
|
||||
"build-umd": "BABEL_ENV=umd babel src/index.js -o dist/index.umd.js",
|
||||
"build-esm": "BABEL_ENV=esm babel src/index.js -o dist/index.esm.js",
|
||||
"build": "npm run build-umd && npm run build-esm && cp src/index.js.flow dist/index.umd.js.flow && cp src/index.js.flow dist/index.esm.js.flow",
|
||||
"build": "tsc",
|
||||
"pretest": "npm run build",
|
||||
"test": "karma start test/karma.config.js",
|
||||
"prepublishOnly": "npm run build",
|
||||
"postpublish": "npm publish --ignore-scripts --@github:registry='https://npm.pkg.github.com'"
|
||||
},
|
||||
"prettier": "@github/prettier-config",
|
||||
"keywords": [
|
||||
"decorator",
|
||||
"remote-form",
|
||||
|
@ -32,21 +30,17 @@
|
|||
],
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.7.0",
|
||||
"@babel/core": "^7.7.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.7.0",
|
||||
"@babel/plugin-transform-modules-umd": "^7.7.0",
|
||||
"babel-preset-github": "^3.2.1",
|
||||
"@github/prettier-config": "0.0.4",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^6.6.0",
|
||||
"eslint-plugin-github": "^3.2.1",
|
||||
"flow-bin": "^0.102.0",
|
||||
"karma": "^4.4.1",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-github": "^3.4.1",
|
||||
"karma": "^5.0.2",
|
||||
"karma-chai": "^0.1.0",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-mocha": "^2.0.0",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"mocha": "^6.2.2"
|
||||
"mocha": "^7.1.1",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"dependencies": {}
|
||||
}
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
/* @flow strict */
|
||||
module.exports = require('eslint-plugin-github/prettier.config')
|
|
@ -1,30 +0,0 @@
|
|||
/* @flow strict */
|
||||
|
||||
type Kicker = {
|
||||
text: () => Promise<SimpleResponse>,
|
||||
json: () => Promise<SimpleResponse>,
|
||||
html: () => Promise<SimpleResponse>
|
||||
}
|
||||
|
||||
type SimpleRequest = {
|
||||
method: string,
|
||||
url: string,
|
||||
body: ?FormData,
|
||||
headers: Headers
|
||||
}
|
||||
|
||||
export type SimpleResponse = {
|
||||
url: string,
|
||||
status: number,
|
||||
statusText: string,
|
||||
headers: Headers,
|
||||
text: string,
|
||||
json: {[string]: any},
|
||||
html: DocumentFragment
|
||||
}
|
||||
export type RemoteFormHandler = (form: HTMLFormElement, kicker: Kicker, req: SimpleRequest) => void | Promise<void>;
|
||||
|
||||
declare export function afterRemote(fn: (form: HTMLFormElement) => void | Promise<void>): void;
|
||||
declare export function beforeRemote(fn: (form: HTMLFormElement) => void | Promise<void>): void;
|
||||
declare export function remoteForm(selector: string, fn: RemoteFormHandler): void;
|
||||
declare export function remoteUninstall(selector: string, fn: RemoteFormHandler): void;
|
|
@ -1,5 +1,3 @@
|
|||
/* @flow strict */
|
||||
|
||||
// Parse HTML text into document fragment.
|
||||
function parseHTML(document: Document, html: string): DocumentFragment {
|
||||
const template = document.createElement('template')
|
||||
|
@ -19,61 +17,59 @@ function serialize(form: HTMLFormElement): string {
|
|||
class ErrorWithResponse extends Error {
|
||||
response: SimpleResponse
|
||||
|
||||
constructor(message, response) {
|
||||
constructor(message: string, response: SimpleResponse) {
|
||||
super(message)
|
||||
this.response = response
|
||||
}
|
||||
}
|
||||
|
||||
function makeDeferred<T>(): [Promise<T>, () => T, () => T] {
|
||||
let resolve
|
||||
let reject
|
||||
const promise = new Promise(function(_resolve, _reject) {
|
||||
function makeDeferred<T>(): [Promise<T>, () => void, () => void] {
|
||||
let resolve: () => void
|
||||
let reject: () => void
|
||||
const promise = new Promise(function (_resolve, _reject) {
|
||||
resolve = _resolve
|
||||
reject = _reject
|
||||
})
|
||||
|
||||
// eslint-disable-next-line flowtype/no-flow-fix-me-comments
|
||||
// $FlowFixMe
|
||||
return [promise, resolve, reject]
|
||||
return [promise as Promise<T>, resolve!, reject!]
|
||||
}
|
||||
|
||||
type SimpleRequest = {
|
||||
method: string,
|
||||
url: string,
|
||||
body: ?FormData,
|
||||
interface SimpleRequest {
|
||||
method: string
|
||||
url: string
|
||||
body: FormData | null
|
||||
headers: Headers
|
||||
}
|
||||
|
||||
export type SimpleResponse = {
|
||||
url: string,
|
||||
status: number,
|
||||
statusText: string,
|
||||
headers: Headers,
|
||||
text: string,
|
||||
// eslint-disable-next-line flowtype/no-weak-types
|
||||
json: {[string]: any},
|
||||
export interface SimpleResponse {
|
||||
url: string
|
||||
status: number
|
||||
statusText: string
|
||||
headers: Headers
|
||||
text: string
|
||||
json: {[key: string]: unknown}
|
||||
html: DocumentFragment
|
||||
}
|
||||
|
||||
type Kicker = {
|
||||
text: () => Promise<SimpleResponse>,
|
||||
json: () => Promise<SimpleResponse>,
|
||||
interface Kicker {
|
||||
text: () => Promise<SimpleResponse>
|
||||
json: () => Promise<SimpleResponse>
|
||||
html: () => Promise<SimpleResponse>
|
||||
}
|
||||
|
||||
export type RemoteFormHandler = (form: HTMLFormElement, kicker: Kicker, req: SimpleRequest) => void
|
||||
|
||||
let formHandlers: Map<string, RemoteFormHandler[]>
|
||||
type Handler = (form: HTMLFormElement) => void
|
||||
|
||||
const afterHandlers = []
|
||||
const beforeHandlers = []
|
||||
const afterHandlers: Handler[] = []
|
||||
const beforeHandlers: Handler[] = []
|
||||
|
||||
export function afterRemote(fn: (form: HTMLFormElement) => mixed) {
|
||||
export function afterRemote(fn: Handler) {
|
||||
afterHandlers.push(fn)
|
||||
}
|
||||
|
||||
export function beforeRemote(fn: (form: HTMLFormElement) => mixed) {
|
||||
export function beforeRemote(fn: Handler) {
|
||||
beforeHandlers.push(fn)
|
||||
}
|
||||
|
||||
|
@ -89,7 +85,10 @@ export function remoteForm(selector: string, fn: RemoteFormHandler) {
|
|||
export function remoteUninstall(selector: string, fn: RemoteFormHandler) {
|
||||
if (formHandlers) {
|
||||
const handlers = formHandlers.get(selector) || []
|
||||
formHandlers.set(selector, handlers.filter(x => x !== fn))
|
||||
formHandlers.set(
|
||||
selector,
|
||||
handlers.filter(x => x !== fn)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -115,11 +114,11 @@ function handleSubmit(event: Event) {
|
|||
}
|
||||
|
||||
const req = buildRequest(form)
|
||||
const [kickerPromise, ultimateResolve, ultimateReject] = makeDeferred()
|
||||
const [kickerPromise, ultimateResolve, ultimateReject] = makeDeferred<SimpleResponse>()
|
||||
|
||||
event.preventDefault()
|
||||
processHandlers(matches, form, req, kickerPromise).then(
|
||||
async performAsyncSubmit => {
|
||||
async (performAsyncSubmit: unknown) => {
|
||||
if (performAsyncSubmit) {
|
||||
for (const handler of beforeHandlers) {
|
||||
await handler(form)
|
||||
|
@ -128,6 +127,7 @@ function handleSubmit(event: Event) {
|
|||
// TODO: ensure that these exceptions are processed by our global error handler
|
||||
remoteSubmit(req)
|
||||
.then(ultimateResolve, ultimateReject)
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
.catch(() => {})
|
||||
.then(() => {
|
||||
for (const handler of afterHandlers) {
|
||||
|
@ -139,7 +139,7 @@ function handleSubmit(event: Event) {
|
|||
form.submit()
|
||||
}
|
||||
},
|
||||
err => {
|
||||
(err: Error) => {
|
||||
// TODO: special "cancel" error object to halt processing and avoid
|
||||
// submitting the form
|
||||
form.submit()
|
||||
|
@ -202,7 +202,7 @@ function buildRequest(form: HTMLFormElement): SimpleRequest {
|
|||
return req
|
||||
}
|
||||
|
||||
async function remoteSubmit(req): Promise<SimpleResponse> {
|
||||
async function remoteSubmit(req: SimpleRequest): Promise<SimpleResponse> {
|
||||
const response = await window.fetch(req.url, {
|
||||
method: req.method,
|
||||
body: req.body !== null ? req.body : undefined,
|
||||
|
@ -217,7 +217,7 @@ async function remoteSubmit(req): Promise<SimpleResponse> {
|
|||
headers: response.headers,
|
||||
text: '',
|
||||
get json() {
|
||||
// eslint-disable-next-line no-shadow
|
||||
// eslint-disable-next-line no-shadow, @typescript-eslint/no-this-alias
|
||||
const response: SimpleResponse = this
|
||||
const data = JSON.parse(response.text)
|
||||
delete response.json
|
||||
|
@ -225,7 +225,7 @@ async function remoteSubmit(req): Promise<SimpleResponse> {
|
|||
return response.json
|
||||
},
|
||||
get html() {
|
||||
// eslint-disable-next-line no-shadow
|
||||
// eslint-disable-next-line no-shadow, @typescript-eslint/no-this-alias
|
||||
const response: SimpleResponse = this
|
||||
delete response.html
|
||||
|
|
@ -20,11 +20,14 @@ function checker(request, response, next) {
|
|||
next()
|
||||
}
|
||||
|
||||
module.exports = function(config) {
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '..',
|
||||
frameworks: ['mocha', 'chai'],
|
||||
files: [{pattern: 'dist/index.esm.js', type: 'module'}, {pattern: 'test/test.js', type: 'module'}],
|
||||
files: [
|
||||
{pattern: 'dist/index.js', type: 'module'},
|
||||
{pattern: 'test/test.js', type: 'module'}
|
||||
],
|
||||
reporters: ['mocha'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
|
|
39
test/test.js
39
test/test.js
|
@ -1,9 +1,9 @@
|
|||
import {remoteForm as _remoteForm, remoteUninstall} from '../dist/index.esm.js'
|
||||
import {remoteForm as _remoteForm, remoteUninstall} from '../dist/index.js'
|
||||
|
||||
describe('remoteForm', function() {
|
||||
describe('remoteForm', function () {
|
||||
let htmlForm
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(function () {
|
||||
document.body.innerHTML = `
|
||||
<form action="/ok" class="my-remote-form remote-widget" method="post" target="submit-fallback">
|
||||
<input name="query" value="hello">
|
||||
|
@ -24,15 +24,15 @@ describe('remoteForm', function() {
|
|||
_remoteForm(selector, fn)
|
||||
}
|
||||
|
||||
afterEach(function() {
|
||||
afterEach(function () {
|
||||
for (const [selector, fn] of installed) {
|
||||
remoteUninstall(selector, fn)
|
||||
}
|
||||
installed.length = 0
|
||||
})
|
||||
|
||||
it('submits the form with fetch', function(done) {
|
||||
remoteForm('.my-remote-form', async function(form, wants, req) {
|
||||
it('submits the form with fetch', function (done) {
|
||||
remoteForm('.my-remote-form', async function (form, wants, req) {
|
||||
assert.ok(req.url.endsWith('/ok'))
|
||||
assert.instanceOf(req.body, FormData)
|
||||
|
||||
|
@ -45,10 +45,10 @@ describe('remoteForm', function() {
|
|||
document.querySelector('button[type=submit]').click()
|
||||
})
|
||||
|
||||
it('server failure scenario', function(done) {
|
||||
it('server failure scenario', function (done) {
|
||||
htmlForm.action = 'server-error'
|
||||
|
||||
remoteForm('.my-remote-form', async function(form, wants) {
|
||||
remoteForm('.my-remote-form', async function (form, wants) {
|
||||
try {
|
||||
await wants.html()
|
||||
assert.ok(false, 'should not resolve')
|
||||
|
@ -62,10 +62,10 @@ describe('remoteForm', function() {
|
|||
document.querySelector('button[type=submit]').click()
|
||||
})
|
||||
|
||||
it('chained handlers', function(done) {
|
||||
it('chained handlers', function (done) {
|
||||
let callbacksCalled = 0
|
||||
|
||||
remoteForm('.remote-widget', async function() {
|
||||
remoteForm('.remote-widget', async function () {
|
||||
callbacksCalled++
|
||||
|
||||
if (callbacksCalled === 2) {
|
||||
|
@ -73,7 +73,7 @@ describe('remoteForm', function() {
|
|||
}
|
||||
})
|
||||
|
||||
remoteForm('.my-remote-form', async function() {
|
||||
remoteForm('.my-remote-form', async function () {
|
||||
callbacksCalled++
|
||||
|
||||
if (callbacksCalled === 2) {
|
||||
|
@ -84,12 +84,12 @@ describe('remoteForm', function() {
|
|||
document.querySelector('button[type=submit]').click()
|
||||
})
|
||||
|
||||
it('exception in js handlers results in form submitting normally', async function() {
|
||||
remoteForm('.remote-widget', function() {
|
||||
it('exception in js handlers results in form submitting normally', async function () {
|
||||
remoteForm('.remote-widget', function () {
|
||||
throw new Error('ignore me')
|
||||
})
|
||||
|
||||
remoteForm('.my-remote-form', async function(form, wants) {
|
||||
remoteForm('.my-remote-form', async function (form, wants) {
|
||||
try {
|
||||
await wants.text()
|
||||
assert.ok(false, 'should never happen')
|
||||
|
@ -102,7 +102,8 @@ describe('remoteForm', function() {
|
|||
event.preventDefault()
|
||||
}
|
||||
const originalMochaError = window.onerror
|
||||
window.onerror = function() {}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
window.onerror = function () {}
|
||||
window.addEventListener('error', errorHandler)
|
||||
|
||||
document.querySelector('button[type=submit]').click()
|
||||
|
@ -115,8 +116,8 @@ describe('remoteForm', function() {
|
|||
assert.match(iframe.contentWindow.location.href, /\/ok$/)
|
||||
})
|
||||
|
||||
it('GET form serializes data to URL', function(done) {
|
||||
remoteForm('.my-remote-form', async function(form, wants, req) {
|
||||
it('GET form serializes data to URL', function (done) {
|
||||
remoteForm('.my-remote-form', async function (form, wants, req) {
|
||||
assert.isNull(req.body)
|
||||
await wants.html()
|
||||
done()
|
||||
|
@ -127,8 +128,8 @@ describe('remoteForm', function() {
|
|||
button.click()
|
||||
})
|
||||
|
||||
it('GET form serializes data to URL with existing query', function(done) {
|
||||
remoteForm('.my-remote-form', async function(form, wants) {
|
||||
it('GET form serializes data to URL with existing query', function (done) {
|
||||
remoteForm('.my-remote-form', async function (form, wants) {
|
||||
await wants.html()
|
||||
done()
|
||||
})
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"include": ["src"],
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "ESNext",
|
||||
"esModuleInterop": true,
|
||||
"lib": ["es6", "dom", "dom.iterable"],
|
||||
"target": "ES2020",
|
||||
"moduleResolution": "node",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"rootDir": "src",
|
||||
"outDir": "dist"
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче