Add PromiseKit dependency
- Added PromiseKit dependency
This commit is contained in:
63
Carthage/Checkouts/PromiseKit/Tests/JS-A+/.gitignore
vendored
Normal file
63
Carthage/Checkouts/PromiseKit/Tests/JS-A+/.gitignore
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
# From https://github.com/github/gitignore/blob/master/Node.gitignore
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
80
Carthage/Checkouts/PromiseKit/Tests/JS-A+/AllTests.swift
vendored
Normal file
80
Carthage/Checkouts/PromiseKit/Tests/JS-A+/AllTests.swift
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
//
|
||||
// AllTests.swift
|
||||
// PMKJSA+Tests
|
||||
//
|
||||
// Created by Lois Di Qual on 2/28/18.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import PromiseKit
|
||||
import JavaScriptCore
|
||||
|
||||
class AllTests: XCTestCase {
|
||||
|
||||
func testAll() {
|
||||
|
||||
let scriptPath = URL(fileURLWithPath: #file).deletingLastPathComponent().appendingPathComponent("build/build.js")
|
||||
guard FileManager.default.fileExists(atPath: scriptPath.path) else {
|
||||
return print("Skipping JS-A+: see README for instructions on how to build")
|
||||
}
|
||||
|
||||
guard let script = try? String(contentsOf: scriptPath) else {
|
||||
return XCTFail("Couldn't read content of test suite JS file")
|
||||
}
|
||||
|
||||
let context = JSUtils.sharedContext
|
||||
|
||||
// Add a global exception handler
|
||||
context.exceptionHandler = { context, exception in
|
||||
guard let exception = exception else {
|
||||
return XCTFail("Unknown JS exception")
|
||||
}
|
||||
JSUtils.printStackTrace(exception: exception, includeExceptionDescription: true)
|
||||
}
|
||||
|
||||
// Setup mock functions (timers, console.log, etc)
|
||||
let environment = MockNodeEnvironment()
|
||||
environment.setup(with: context)
|
||||
|
||||
// Expose JSPromise in the javascript context
|
||||
context.setObject(JSPromise.self, forKeyedSubscript: "JSPromise" as NSString)
|
||||
|
||||
// Create adapter
|
||||
guard let adapter = JSValue(object: NSDictionary(), in: context) else {
|
||||
fatalError("Couldn't create adapter")
|
||||
}
|
||||
adapter.setObject(JSAdapter.resolved, forKeyedSubscript: "resolved" as NSString)
|
||||
adapter.setObject(JSAdapter.rejected, forKeyedSubscript: "rejected" as NSString)
|
||||
adapter.setObject(JSAdapter.deferred, forKeyedSubscript: "deferred" as NSString)
|
||||
|
||||
// Evaluate contents of `build.js`, which exposes `runTests` in the global context
|
||||
context.evaluateScript(script)
|
||||
guard let runTests = context.objectForKeyedSubscript("runTests") else {
|
||||
return XCTFail("Couldn't find `runTests` in JS context")
|
||||
}
|
||||
|
||||
// Create a callback that's called whenever there's a failure
|
||||
let onFail: @convention(block) (JSValue, JSValue) -> Void = { test, error in
|
||||
guard let test = test.toString(), let error = error.toString() else {
|
||||
return XCTFail("Unknown test failure")
|
||||
}
|
||||
XCTFail("\(test) failed: \(error)")
|
||||
}
|
||||
let onFailValue: JSValue = JSValue(object: onFail, in: context)
|
||||
|
||||
// Create a new callback that we'll send to `runTest` so that it notifies when tests are done running.
|
||||
let expectation = self.expectation(description: "async")
|
||||
let onDone: @convention(block) (JSValue) -> Void = { failures in
|
||||
expectation.fulfill()
|
||||
}
|
||||
let onDoneValue: JSValue = JSValue(object: onDone, in: context)
|
||||
|
||||
// If there's a need to only run one specific test, uncomment the next line and comment the one after
|
||||
// let testName: JSValue = JSValue(object: "2.3.1", in: context)
|
||||
let testName = JSUtils.undefined
|
||||
|
||||
// Call `runTests`
|
||||
runTests.call(withArguments: [adapter, onFailValue, onDoneValue, testName])
|
||||
self.wait(for: [expectation], timeout: 60)
|
||||
}
|
||||
}
|
||||
53
Carthage/Checkouts/PromiseKit/Tests/JS-A+/JSAdapter.swift
vendored
Normal file
53
Carthage/Checkouts/PromiseKit/Tests/JS-A+/JSAdapter.swift
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// JSAdapter.swift
|
||||
// PMKJSA+Tests
|
||||
//
|
||||
// Created by Lois Di Qual on 3/2/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import JavaScriptCore
|
||||
import PromiseKit
|
||||
|
||||
enum JSAdapter {
|
||||
|
||||
static let resolved: @convention(block) (JSValue) -> JSPromise = { value in
|
||||
return JSPromise(promise: .value(value))
|
||||
}
|
||||
|
||||
static let rejected: @convention(block) (JSValue) -> JSPromise = { reason in
|
||||
let error = JSUtils.JSError(reason: reason)
|
||||
let promise = Promise<JSValue>(error: error)
|
||||
return JSPromise(promise: promise)
|
||||
}
|
||||
|
||||
static let deferred: @convention(block) () -> JSValue = {
|
||||
|
||||
let context = JSContext.current()
|
||||
|
||||
guard let object = JSValue(object: NSDictionary(), in: context) else {
|
||||
fatalError("Couldn't create object")
|
||||
}
|
||||
|
||||
let pendingPromise = Promise<JSValue>.pending()
|
||||
let jsPromise = JSPromise(promise: pendingPromise.promise)
|
||||
|
||||
// promise
|
||||
object.setObject(jsPromise, forKeyedSubscript: "promise" as NSString)
|
||||
|
||||
// resolve
|
||||
let resolve: @convention(block) (JSValue) -> Void = { value in
|
||||
pendingPromise.resolver.fulfill(value)
|
||||
}
|
||||
object.setObject(resolve, forKeyedSubscript: "resolve" as NSString)
|
||||
|
||||
// reject
|
||||
let reject: @convention(block) (JSValue) -> Void = { reason in
|
||||
let error = JSUtils.JSError(reason: reason)
|
||||
pendingPromise.resolver.reject(error)
|
||||
}
|
||||
object.setObject(reject, forKeyedSubscript: "reject" as NSString)
|
||||
|
||||
return object
|
||||
}
|
||||
}
|
||||
94
Carthage/Checkouts/PromiseKit/Tests/JS-A+/JSPromise.swift
vendored
Normal file
94
Carthage/Checkouts/PromiseKit/Tests/JS-A+/JSPromise.swift
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// JSPromise.swift
|
||||
// PMKJSA+Tests
|
||||
//
|
||||
// Created by Lois Di Qual on 3/1/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import XCTest
|
||||
import PromiseKit
|
||||
import JavaScriptCore
|
||||
|
||||
@objc protocol JSPromiseProtocol: JSExport {
|
||||
func then(_: JSValue, _: JSValue) -> JSPromise
|
||||
}
|
||||
|
||||
class JSPromise: NSObject, JSPromiseProtocol {
|
||||
|
||||
let promise: Promise<JSValue>
|
||||
|
||||
init(promise: Promise<JSValue>) {
|
||||
self.promise = promise
|
||||
}
|
||||
|
||||
func then(_ onFulfilled: JSValue, _ onRejected: JSValue) -> JSPromise {
|
||||
|
||||
// Keep a reference to the returned promise so we can comply to 2.3.1
|
||||
var returnedPromiseRef: Promise<JSValue>?
|
||||
|
||||
let afterFulfill = promise.then { value -> Promise<JSValue> in
|
||||
|
||||
// 2.2.1: ignored if not a function
|
||||
guard JSUtils.isFunction(value: onFulfilled) else {
|
||||
return .value(value)
|
||||
}
|
||||
|
||||
// Call `onFulfilled`
|
||||
// 2.2.5: onFulfilled/onRejected must be called as functions (with no `this` value)
|
||||
guard let returnValue = try JSUtils.call(function: onFulfilled, arguments: [JSUtils.undefined, value]) else {
|
||||
return .value(value)
|
||||
}
|
||||
|
||||
// Extract JSPromise.promise if available, or use plain return value
|
||||
if let jsPromise = returnValue.toObjectOf(JSPromise.self) as? JSPromise {
|
||||
|
||||
// 2.3.1: if returned value is the promise that `then` returned, throw TypeError
|
||||
if jsPromise.promise === returnedPromiseRef {
|
||||
throw JSUtils.JSError(reason: JSUtils.typeError(message: "Returned self"))
|
||||
}
|
||||
return jsPromise.promise
|
||||
} else {
|
||||
return .value(returnValue)
|
||||
}
|
||||
}
|
||||
|
||||
let afterReject = promise.recover { error -> Promise<JSValue> in
|
||||
|
||||
// 2.2.1: ignored if not a function
|
||||
guard let jsError = error as? JSUtils.JSError, JSUtils.isFunction(value: onRejected) else {
|
||||
throw error
|
||||
}
|
||||
|
||||
// Call `onRejected`
|
||||
// 2.2.5: onFulfilled/onRejected must be called as functions (with no `this` value)
|
||||
guard let returnValue = try JSUtils.call(function: onRejected, arguments: [JSUtils.undefined, jsError.reason]) else {
|
||||
throw error
|
||||
}
|
||||
|
||||
// Extract JSPromise.promise if available, or use plain return value
|
||||
if let jsPromise = returnValue.toObjectOf(JSPromise.self) as? JSPromise {
|
||||
|
||||
// 2.3.1: if returned value is the promise that `then` returned, throw TypeError
|
||||
if jsPromise.promise === returnedPromiseRef {
|
||||
throw JSUtils.JSError(reason: JSUtils.typeError(message: "Returned self"))
|
||||
}
|
||||
return jsPromise.promise
|
||||
} else {
|
||||
return .value(returnValue)
|
||||
}
|
||||
}
|
||||
|
||||
let newPromise = Promise<Result<JSValue>> { resolver in
|
||||
_ = promise.tap(resolver.fulfill)
|
||||
}.then(on: nil) { result -> Promise<JSValue> in
|
||||
switch result {
|
||||
case .fulfilled: return afterFulfill
|
||||
case .rejected: return afterReject
|
||||
}
|
||||
}
|
||||
returnedPromiseRef = newPromise
|
||||
|
||||
return JSPromise(promise: newPromise)
|
||||
}
|
||||
}
|
||||
116
Carthage/Checkouts/PromiseKit/Tests/JS-A+/JSUtils.swift
vendored
Normal file
116
Carthage/Checkouts/PromiseKit/Tests/JS-A+/JSUtils.swift
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// JSUtils.swift
|
||||
// PMKJSA+Tests
|
||||
//
|
||||
// Created by Lois Di Qual on 3/2/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import JavaScriptCore
|
||||
|
||||
enum JSUtils {
|
||||
|
||||
class JSError: Error {
|
||||
let reason: JSValue
|
||||
init(reason: JSValue) {
|
||||
self.reason = reason
|
||||
}
|
||||
}
|
||||
|
||||
static let sharedContext: JSContext = {
|
||||
guard let context = JSContext() else {
|
||||
fatalError("Couldn't create JS context")
|
||||
}
|
||||
return context
|
||||
}()
|
||||
|
||||
static var undefined: JSValue {
|
||||
guard let undefined = JSValue(undefinedIn: JSUtils.sharedContext) else {
|
||||
fatalError("Couldn't create `undefined` value")
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
static func typeError(message: String) -> JSValue {
|
||||
let message = message.replacingOccurrences(of: "\"", with: "\\\"")
|
||||
let script = "new TypeError(\"\(message)\")"
|
||||
guard let result = sharedContext.evaluateScript(script) else {
|
||||
fatalError("Couldn't create TypeError")
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// @warning: relies on lodash to be present
|
||||
static func isFunction(value: JSValue) -> Bool {
|
||||
guard let context = value.context else {
|
||||
return false
|
||||
}
|
||||
guard let lodash = context.objectForKeyedSubscript("_") else {
|
||||
fatalError("Couldn't get lodash in JS context")
|
||||
}
|
||||
guard let result = lodash.invokeMethod("isFunction", withArguments: [value]) else {
|
||||
fatalError("Couldn't invoke _.isFunction")
|
||||
}
|
||||
return result.toBool()
|
||||
}
|
||||
|
||||
// Calls a JS function using `Function.prototype.call` and throws any potential exception wrapped in a JSError
|
||||
static func call(function: JSValue, arguments: [JSValue]) throws -> JSValue? {
|
||||
|
||||
let context = JSUtils.sharedContext
|
||||
|
||||
// Create a new exception handler that will store a potential exception
|
||||
// thrown in the handler. Save the value of the old handler.
|
||||
var caughtException: JSValue?
|
||||
let savedExceptionHandler = context.exceptionHandler
|
||||
context.exceptionHandler = { context, exception in
|
||||
caughtException = exception
|
||||
}
|
||||
|
||||
// Call the handler
|
||||
let returnValue = function.invokeMethod("call", withArguments: arguments)
|
||||
context.exceptionHandler = savedExceptionHandler
|
||||
|
||||
// If an exception was caught, throw it
|
||||
if let exception = caughtException {
|
||||
throw JSError(reason: exception)
|
||||
}
|
||||
|
||||
return returnValue
|
||||
}
|
||||
|
||||
static func printCurrentStackTrace() {
|
||||
guard let exception = JSUtils.sharedContext.evaluateScript("new Error()") else {
|
||||
return print("Couldn't get current stack trace")
|
||||
}
|
||||
printStackTrace(exception: exception, includeExceptionDescription: false)
|
||||
}
|
||||
|
||||
static func printStackTrace(exception: JSValue, includeExceptionDescription: Bool) {
|
||||
guard let lineNumber = exception.objectForKeyedSubscript("line"),
|
||||
let column = exception.objectForKeyedSubscript("column"),
|
||||
let message = exception.objectForKeyedSubscript("message"),
|
||||
let stacktrace = exception.objectForKeyedSubscript("stack")?.toString() else {
|
||||
return print("Couldn't print stack trace")
|
||||
}
|
||||
|
||||
if includeExceptionDescription {
|
||||
print("JS Exception at \(lineNumber):\(column): \(message)")
|
||||
}
|
||||
|
||||
let lines = stacktrace.split(separator: "\n").map { "\t> \($0)" }.joined(separator: "\n")
|
||||
print(lines)
|
||||
}
|
||||
}
|
||||
|
||||
#if !swift(>=3.2)
|
||||
extension String {
|
||||
func split(separator: Character, omittingEmptySubsequences: Bool = true) -> [String] {
|
||||
return characters.split(separator: separator, omittingEmptySubsequences: omittingEmptySubsequences).map(String.init)
|
||||
}
|
||||
|
||||
var first: Character? {
|
||||
return characters.first
|
||||
}
|
||||
}
|
||||
#endif
|
||||
117
Carthage/Checkouts/PromiseKit/Tests/JS-A+/MockNodeEnvironment.swift
vendored
Normal file
117
Carthage/Checkouts/PromiseKit/Tests/JS-A+/MockNodeEnvironment.swift
vendored
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// MockNodeEnvironment.swift
|
||||
// PMKJSA+Tests
|
||||
//
|
||||
// Created by Lois Di Qual on 3/1/18.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import JavaScriptCore
|
||||
|
||||
class MockNodeEnvironment {
|
||||
|
||||
private var timers: [UInt32: Timer] = [:]
|
||||
|
||||
func setup(with context: JSContext) {
|
||||
|
||||
// console.log / console.error
|
||||
setupConsole(context: context)
|
||||
|
||||
// setTimeout
|
||||
let setTimeout: @convention(block) (JSValue, Double) -> UInt32 = { function, intervalMs in
|
||||
let timerID = self.addTimer(interval: intervalMs / 1000, repeats: false, function: function)
|
||||
return timerID
|
||||
}
|
||||
context.setObject(setTimeout, forKeyedSubscript: "setTimeout" as NSString)
|
||||
|
||||
// clearTimeout
|
||||
let clearTimeout: @convention(block) (JSValue) -> Void = { timeoutID in
|
||||
guard timeoutID.isNumber else {
|
||||
return
|
||||
}
|
||||
self.removeTimer(timerID: timeoutID.toUInt32())
|
||||
}
|
||||
context.setObject(clearTimeout, forKeyedSubscript: "clearTimeout" as NSString)
|
||||
|
||||
// setInterval
|
||||
let setInterval: @convention(block) (JSValue, Double) -> UInt32 = { function, intervalMs in
|
||||
let timerID = self.addTimer(interval: intervalMs / 1000, repeats: true, function: function)
|
||||
return timerID
|
||||
}
|
||||
context.setObject(setInterval, forKeyedSubscript: "setInterval" as NSString)
|
||||
|
||||
// clearInterval
|
||||
let clearInterval: @convention(block) (JSValue) -> Void = { intervalID in
|
||||
guard intervalID.isNumber else {
|
||||
return
|
||||
}
|
||||
self.removeTimer(timerID: intervalID.toUInt32())
|
||||
}
|
||||
context.setObject(clearInterval, forKeyedSubscript: "clearInterval" as NSString)
|
||||
}
|
||||
|
||||
private func setupConsole(context: JSContext) {
|
||||
|
||||
guard let console = context.objectForKeyedSubscript("console") else {
|
||||
fatalError("Couldn't get global `console` object")
|
||||
}
|
||||
|
||||
let consoleLog: @convention(block) () -> Void = {
|
||||
guard let arguments = JSContext.currentArguments(), let format = arguments.first as? JSValue else {
|
||||
return
|
||||
}
|
||||
|
||||
let otherArguments = arguments.dropFirst()
|
||||
if otherArguments.count == 0 {
|
||||
print(format)
|
||||
} else {
|
||||
|
||||
let otherArguments = otherArguments.compactMap { $0 as? JSValue }
|
||||
let format = format.toString().replacingOccurrences(of: "%s", with: "%@")
|
||||
let expectedTypes = format.split(separator: "%", omittingEmptySubsequences: false).dropFirst().compactMap { $0.first }.map { String($0) }
|
||||
|
||||
let typedArguments = otherArguments.enumerated().compactMap { index, value -> CVarArg? in
|
||||
let expectedType = expectedTypes[index]
|
||||
let converted: CVarArg
|
||||
switch expectedType {
|
||||
case "s": converted = value.toString()
|
||||
case "d": converted = value.toInt32()
|
||||
case "f": converted = value.toDouble()
|
||||
default: converted = value.toString()
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
let output = String(format: format, arguments: typedArguments)
|
||||
print(output)
|
||||
}
|
||||
}
|
||||
console.setObject(consoleLog, forKeyedSubscript: "log" as NSString)
|
||||
console.setObject(consoleLog, forKeyedSubscript: "error" as NSString)
|
||||
}
|
||||
|
||||
private func addTimer(interval: TimeInterval, repeats: Bool, function: JSValue) -> UInt32 {
|
||||
let block = BlockOperation {
|
||||
DispatchQueue.main.async {
|
||||
function.call(withArguments: [])
|
||||
}
|
||||
}
|
||||
let timer = Timer.scheduledTimer(timeInterval: interval, target: block, selector: #selector(Operation.main), userInfo: nil, repeats: repeats)
|
||||
let rawHash = UUID().uuidString.hashValue
|
||||
#if swift(>=4.0)
|
||||
let hash = UInt32(truncatingIfNeeded: rawHash)
|
||||
#else
|
||||
let hash = UInt32(truncatingBitPattern: rawHash)
|
||||
#endif
|
||||
timers[hash] = timer
|
||||
return hash
|
||||
}
|
||||
|
||||
private func removeTimer(timerID: UInt32) {
|
||||
guard let timer = timers[timerID] else {
|
||||
return print("Couldn't find timer \(timerID)")
|
||||
}
|
||||
timer.invalidate()
|
||||
timers[timerID] = nil
|
||||
}
|
||||
}
|
||||
75
Carthage/Checkouts/PromiseKit/Tests/JS-A+/README.md
vendored
Normal file
75
Carthage/Checkouts/PromiseKit/Tests/JS-A+/README.md
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
Promises/A+ Compliance Test Suite (JavaScript)
|
||||
==============================================
|
||||
|
||||
What is this?
|
||||
-------------
|
||||
|
||||
This contains the necessary Swift and JS files to run the Promises/A+ compliance test suite from PromiseKit's unit tests.
|
||||
|
||||
- Promise/A+ Spec: <https://promisesaplus.com/>
|
||||
- Compliance Test Suite: <https://github.com/promises-aplus/promises-tests>
|
||||
|
||||
Run tests
|
||||
---------
|
||||
|
||||
```
|
||||
$ npm install
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
then open `PromiseKit.xcodeproj` and run the `PMKJSA+Tests` unit test scheme.
|
||||
|
||||
Known limitations
|
||||
-----------------
|
||||
|
||||
See `ignoredTests` in `index.js`.
|
||||
|
||||
|
||||
- 2.3.3 is disabled: Otherwise, if x is an object or function. This spec is a NOOP for Swift:
|
||||
- We have decided not to interact with other Promises A+ implementations
|
||||
- functions cannot have properties
|
||||
|
||||
Upgrade the test suite
|
||||
----------------------
|
||||
|
||||
```
|
||||
$ npm install --save promises-aplus-tests@latest
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
Develop
|
||||
-------
|
||||
|
||||
JavaScriptCore is a bit tedious to work with so here are a couple tips in case you're trying to debug the test suite.
|
||||
|
||||
If you're editing JS files, enable live rebuilds:
|
||||
|
||||
```
|
||||
$ npm run watch
|
||||
```
|
||||
|
||||
If you're editing Swift files, a couple things you can do:
|
||||
|
||||
- You can adjust `testName` in `AllTests.swift` to only run one test suite
|
||||
- You can call `JSUtils.printCurrentStackTrace()` at any time. It won't contain line numbers but some of the frame names might help.
|
||||
|
||||
How it works
|
||||
------------
|
||||
|
||||
The Promises/A+ test suite is written in JavaScript but PromiseKit is written in Swift/ObjC. For the test suite to run against swift code, we expose a promise wrapper `JSPromise` inside a JavaScriptCore context. This is done in a regular XCTestCase.
|
||||
|
||||
Since JavaScriptCore doesn't support CommonJS imports, we inline all the JavaScript code into `build/build.js` using webpack. This includes all the npm dependencies (`promises-aplus-tests`, `mocha`, `sinon`, etc) as well as the glue code in `index.js`.
|
||||
|
||||
`build.js` exposes one global variable `runTests(adapter, onFail, onDone, [testName])`. In our XCTestCase, a shared JavaScriptCore context is created, `build.js` is evaluated and now `runTests` is accessible from the Swift context.
|
||||
|
||||
In our swift test, we create a JS-bridged `JSPromise` which only has one method `then(onFulfilled, onRejected) -> Promise`. It wraps a swift `Promise` and delegates call `then` calls to it.
|
||||
|
||||
An [adapter](https://github.com/promises-aplus/promises-tests#adapters) – plain JS object which provides `revoled(value), rejected(reason), and deferred()` – is passed to `runTests` to run the whole JavaScript test suite.
|
||||
|
||||
Errors and end events are reported back to Swift and piped to `XCTFail()` if necessary.
|
||||
|
||||
Since JavaScriptCore isn't a node/web environment, there is quite a bit of stubbing necessary for all this to work:
|
||||
|
||||
- The `fs` module is stubbed with an empty function
|
||||
- `console.log` redirects to `Swift.print` and provides only basic format parsing
|
||||
- `setTimeout/setInterval` are implemented with `Swift.Timer` behind the scenes and stored in a `[TimerID: Timer]` map.
|
||||
42
Carthage/Checkouts/PromiseKit/Tests/JS-A+/index.js
vendored
Normal file
42
Carthage/Checkouts/PromiseKit/Tests/JS-A+/index.js
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
const _ = require('lodash')
|
||||
require('mocha')
|
||||
|
||||
// Ignored by design
|
||||
const ignoredTests = [
|
||||
'2.3.3'
|
||||
]
|
||||
|
||||
module.exports = function(adapter, onFail, onDone, testName) {
|
||||
|
||||
global.adapter = adapter
|
||||
const mocha = new Mocha({ ui: 'bdd' })
|
||||
|
||||
// Require all tests
|
||||
console.log('Loading test files')
|
||||
const requireTest = require.context('promises-aplus-tests/lib/tests', false, /\.js$/)
|
||||
requireTest.keys().forEach(file => {
|
||||
|
||||
let currentTestName = _.replace(_.replace(file, './', ''), '.js', '')
|
||||
if (testName && currentTestName !== testName) {
|
||||
return
|
||||
}
|
||||
|
||||
if (_.includes(ignoredTests, currentTestName)) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log(`\t${currentTestName}`)
|
||||
mocha.suite.emit('pre-require', global, file, mocha)
|
||||
mocha.suite.emit('require', requireTest(file), file, mocha)
|
||||
mocha.suite.emit('post-require', global, file, mocha)
|
||||
})
|
||||
|
||||
const runner = mocha.run(failures => {
|
||||
onDone(failures)
|
||||
})
|
||||
|
||||
runner.on('fail', (test, err) => {
|
||||
console.error(err)
|
||||
onFail(test.title, err)
|
||||
})
|
||||
}
|
||||
7869
Carthage/Checkouts/PromiseKit/Tests/JS-A+/package-lock.json
generated
vendored
Normal file
7869
Carthage/Checkouts/PromiseKit/Tests/JS-A+/package-lock.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
17
Carthage/Checkouts/PromiseKit/Tests/JS-A+/package.json
vendored
Normal file
17
Carthage/Checkouts/PromiseKit/Tests/JS-A+/package.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"scripts": {
|
||||
"build": "webpack-cli",
|
||||
"watch": "webpack-cli --watch --mode development"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.3",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"lodash": "^4.17.5",
|
||||
"mocha": "^5.0.1",
|
||||
"promises-aplus-tests": "^2.1.2",
|
||||
"sinon": "^4.4.2",
|
||||
"webpack": "^4.0.1",
|
||||
"webpack-cli": "^2.0.9"
|
||||
}
|
||||
}
|
||||
29
Carthage/Checkouts/PromiseKit/Tests/JS-A+/webpack.config.js
vendored
Normal file
29
Carthage/Checkouts/PromiseKit/Tests/JS-A+/webpack.config.js
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
var webpack = require('webpack');
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
context: __dirname,
|
||||
entry: './index.js',
|
||||
output: {
|
||||
path: __dirname + '/build',
|
||||
filename: 'build.js',
|
||||
library: 'runTests'
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
exclude: /(node_modules)/,
|
||||
use: {
|
||||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: ['env']
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
node: {
|
||||
fs: 'empty'
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user