@@ -48,11 +52,8 @@ function loadingDisplaying(p5, fn){
* let img;
*
* // Load the image and create a p5.Image object.
- * function preload() {
- * img = loadImage('assets/laDefense.jpg');
- * }
- *
- * function setup() {
+ * async function setup() {
+ * img = await loadImage('assets/laDefense.jpg');
* createCanvas(100, 100);
*
* // Draw the image.
@@ -107,110 +108,60 @@ function loadingDisplaying(p5, fn){
failureCallback
) {
p5._validateParameters('loadImage', arguments);
- const pImg = new p5.Image(1, 1, this);
- const self = this;
- const req = new Request(path, {
- method: 'GET',
- mode: 'cors'
- });
+ try{
+ let pImg = new p5.Image(1, 1, this);
- return fetch(path, req)
- .then(async response => {
- // GIF section
- const contentType = response.headers.get('content-type');
- if (contentType === null) {
- console.warn(
- 'The image you loaded does not have a Content-Type header. If you are using the online editor consider reuploading the asset.'
- );
- }
- if (contentType && contentType.includes('image/gif')) {
- await response.arrayBuffer().then(
- arrayBuffer => new Promise((resolve, reject) => {
- if (arrayBuffer) {
- const byteArray = new Uint8Array(arrayBuffer);
- try{
- _createGif(
- byteArray,
- pImg,
- successCallback,
- failureCallback,
- (pImg => {
- resolve(pImg);
- }).bind(self)
- );
- }catch(e){
- console.error(e.toString(), e.stack);
- if (typeof failureCallback === 'function') {
- failureCallback(e);
- } else {
- console.error(e);
- }
- reject(e);
- }
- }
- })
- ).catch(
- e => {
- if (typeof failureCallback === 'function') {
- failureCallback(e);
- } else {
- console.error(e);
- }
- }
- );
- } else {
- // Non-GIF Section
- const img = new Image();
-
- await new Promise((resolve, reject) => {
- img.onload = () => {
- pImg.width = pImg.canvas.width = img.width;
- pImg.height = pImg.canvas.height = img.height;
-
- // Draw the image into the backing canvas of the p5.Image
- pImg.drawingContext.drawImage(img, 0, 0);
- pImg.modified = true;
- if (typeof successCallback === 'function') {
- successCallback(pImg);
- }
- resolve();
- };
-
- img.onerror = e => {
- p5._friendlyFileLoadError(0, img.src);
- if (typeof failureCallback === 'function') {
- failureCallback(e);
- } else {
- console.error(e);
- }
- reject();
- };
-
- // Set crossOrigin in case image is served with CORS headers.
- // This will let us draw to the canvas without tainting it.
- // See https://developer.mozilla.org/en-US/docs/HTML/CORS_Enabled_Image
- // When using data-uris the file will be loaded locally
- // so we don't need to worry about crossOrigin with base64 file types.
- if (path.indexOf('data:image/') !== 0) {
- img.crossOrigin = 'Anonymous';
- }
- // start loading the image
- img.src = path;
- });
- }
- pImg.modified = true;
- return pImg;
- })
- .catch(e => {
- p5._friendlyFileLoadError(0, path);
- if (typeof failureCallback === 'function') {
- failureCallback(e);
- } else {
- console.error(e);
- }
+ const req = new Request(path, {
+ method: 'GET',
+ mode: 'cors'
});
- // return pImg;
+
+ const { data, headers } = await request(req, 'bytes');
+
+ // GIF section
+ const contentType = headers.get('content-type');
+
+ if (contentType === null) {
+ console.warn(
+ 'The image you loaded does not have a Content-Type header. If you are using the online editor consider reuploading the asset.'
+ );
+ }
+
+ if (contentType && contentType.includes('image/gif')) {
+ await _createGif(
+ data,
+ pImg
+ );
+
+ } else {
+ // Non-GIF Section
+ const blob = new Blob([data]);
+ const img = await createImageBitmap(blob);
+
+ pImg.width = pImg.canvas.width = img.width;
+ pImg.height = pImg.canvas.height = img.height;
+
+ // Draw the image into the backing canvas of the p5.Image
+ pImg.drawingContext.drawImage(img, 0, 0);
+ }
+
+ pImg.modified = true;
+
+ if(successCallback){
+ return successCallback(pImg);
+ }else{
+ return pImg;
+ }
+
+ } catch(err) {
+ p5._friendlyFileLoadError(0, path);
+ if (typeof failureCallback === 'function') {
+ return failureCallback(err);
+ } else {
+ throw err;
+ }
+ }
};
/**
@@ -651,31 +602,25 @@ function loadingDisplaying(p5, fn){
/**
* Helper function for loading GIF-based images
*/
- function _createGif(
- arrayBuffer,
- pImg,
- successCallback,
- failureCallback,
- finishCallback
- ) {
+ async function _createGif(arrayBuffer, pImg) {
+ // TODO: Replace with ImageDecoder once it is widely available
+ // https://developer.mozilla.org/en-US/docs/Web/API/ImageDecoder
const gifReader = new omggif.GifReader(arrayBuffer);
pImg.width = pImg.canvas.width = gifReader.width;
pImg.height = pImg.canvas.height = gifReader.height;
const frames = [];
const numFrames = gifReader.numFrames();
let framePixels = new Uint8ClampedArray(pImg.width * pImg.height * 4);
+
const loadGIFFrameIntoImage = (frameNum, gifReader) => {
try {
gifReader.decodeAndBlitFrameRGBA(frameNum, framePixels);
} catch (e) {
p5._friendlyFileLoadError(8, pImg.src);
- if (typeof failureCallback === 'function') {
- failureCallback(e);
- } else {
- console.error(e);
- }
+ throw e;
}
};
+
for (let j = 0; j < numFrames; j++) {
const frameInfo = gifReader.frameInfo(j);
const prevFrameData = pImg.drawingContext.getImageData(
@@ -764,10 +709,7 @@ function loadingDisplaying(p5, fn){
};
}
- if (typeof successCallback === 'function') {
- successCallback(pImg);
- }
- finishCallback();
+ return pImg;
}
/**
diff --git a/src/image/p5.Image.js b/src/image/p5.Image.js
index 3cb428313c..b87827be81 100644
--- a/src/image/p5.Image.js
+++ b/src/image/p5.Image.js
@@ -283,8 +283,6 @@ class Image {
*
*
*/
- /**
- */
updatePixels(x, y, w, h) {
// Renderer2D.prototype.updatePixels.call(this, x, y, w, h);
const pixelsState = this._pixelsState;
@@ -472,7 +470,7 @@ class Image {
return region;
}
- _getPixel(...args) {
+ _getPixel(x, y) {
let imageData, index;
imageData = this.drawingContext.getImageData(x, y, 1, 1).data;
index = 0;
@@ -1454,7 +1452,7 @@ class Image {
/**
* Saves the image to a file.
*
- * By default, `img.save()` saves the image as a PNG image called
+ * By default, `img.save()` saves the image as a PNG image called
* `untitled.png`.
*
* The first parameter, `filename`, is optional. It's a string that sets the
@@ -1517,6 +1515,12 @@ class Image {
}
}
+ async toBlob() {
+ return new Promise(resolve => {
+ this.canvas.toBlob(resolve);
+ });
+ }
+
// GIF Section
/**
* Restarts an animated GIF at its first frame.
diff --git a/src/io/files.js b/src/io/files.js
index ba7dd0b8ef..bd588d4aa9 100644
--- a/src/io/files.js
+++ b/src/io/files.js
@@ -6,6 +6,71 @@
*/
import * as fileSaver from 'file-saver';
+import { Renderer } from '../core/p5.Renderer';
+import { Graphics } from '../core/p5.Graphics';
+
+class HTTPError extends Error {
+ status;
+ response;
+ ok;
+}
+
+export async function request(path, type){
+ try {
+ const res = await fetch(path);
+
+ if (res.ok) {
+ let data;
+ switch(type) {
+ case 'json':
+ data = await res.json();
+ break;
+ case 'text':
+ data = await res.text();
+ break;
+ case 'arrayBuffer':
+ data = await res.arrayBuffer();
+ break;
+ case 'blob':
+ data = await res.blob();
+ break;
+ case 'bytes':
+ // TODO: Chrome does not implement res.bytes() yet
+ if(res.bytes){
+ data = await res.bytes();
+ }else{
+ const d = await res.arrayBuffer();
+ data = new Uint8Array(d);
+ }
+ break;
+ default:
+ throw new Error('Unsupported response type');
+ }
+
+ return { data, headers: res.headers };
+
+ } else {
+ const err = new HTTPError(res.statusText);
+ err.status = res.status;
+ err.response = res;
+ err.ok = false;
+
+ throw err;
+ }
+
+ } catch(err) {
+ // Handle both fetch error and HTTP error
+ if (err instanceof TypeError) {
+ console.log('You may have encountered a CORS error');
+ } else if (err instanceof HTTPError) {
+ console.log('You have encountered a HTTP error');
+ } else if (err instanceof SyntaxError) {
+ console.log('There is an error parsing the response to requested data structure');
+ }
+
+ throw err;
+ }
+}
function files(p5, fn){
/**
@@ -18,31 +83,35 @@ function files(p5, fn){
* data in an object with strings as keys. Values can be strings, numbers,
* Booleans, arrays, `null`, or other objects.
*
- * The first parameter, `path`, is always a string with the path to the file.
+ * The first parameter, `path`, is a string with the path to the file.
* Paths to local files should be relative, as in
* `loadJSON('assets/data.json')`. URLs such as
* `'https://example.com/data.json'` may be blocked due to browser security.
+ * The `path` parameter can also be defined as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
+ * object for more advanced usage.
*
* The second parameter, `successCallback`, is optional. If a function is
* passed, as in `loadJSON('assets/data.json', handleData)`, then the
* `handleData()` function will be called once the data loads. The object
* created from the JSON data will be passed to `handleData()` as its only argument.
+ * The return value of the `handleData()` function will be used as the final return
+ * value of `loadJSON('assets/data.json', handleData)`.
*
* The third parameter, `failureCallback`, is also optional. If a function is
* passed, as in `loadJSON('assets/data.json', handleData, handleFailure)`,
* then the `handleFailure()` function will be called if an error occurs while
* loading. The `Error` object will be passed to `handleFailure()` as its only
- * argument.
+ * argument. The return value of the `handleFailure()` function will be used as the
+ * final return value of `loadJSON('assets/data.json', handleData, handleFailure)`.
*
- * Note: Data can take time to load. Calling `loadJSON()` within
- * } object containing the loaded data.
*
* @example
*
@@ -50,12 +119,8 @@ function files(p5, fn){
*
* let myData;
*
- * // Load the JSON and create an object.
- * function preload() {
- * myData = loadJSON('assets/data.json');
- * }
- *
- * function setup() {
+ * async function setup() {
+ * myData = await loadJSON('assets/data.json');
* createCanvas(100, 100);
*
* background(200);
@@ -76,12 +141,8 @@ function files(p5, fn){
*
* let myData;
*
- * // Load the JSON and create an object.
- * function preload() {
- * myData = loadJSON('assets/data.json');
- * }
- *
- * function setup() {
+ * async function setup() {
+ * myData = await loadJSON('assets/data.json');
* createCanvas(100, 100);
*
* background(200);
@@ -109,12 +170,8 @@ function files(p5, fn){
*
* let myData;
*
- * // Load the GeoJSON and create an object.
- * function preload() {
- * myData = loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson');
- * }
- *
- * function setup() {
+ * async function setup() {
+ * myData = await loadJSON('https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson');
* createCanvas(100, 100);
*
* background(200);
@@ -143,14 +200,12 @@ function files(p5, fn){
* let bigQuake;
*
* // Load the GeoJSON and preprocess it.
- * function preload() {
- * loadJSON(
+ * async function setup() {
+ * await loadJSON(
* 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson',
* handleData
* );
- * }
*
- * function setup() {
* createCanvas(100, 100);
*
* background(200);
@@ -189,15 +244,13 @@ function files(p5, fn){
* let bigQuake;
*
* // Load the GeoJSON and preprocess it.
- * function preload() {
- * loadJSON(
+ * async function setup() {
+ * await loadJSON(
* 'https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson',
* handleData,
* handleError
* );
- * }
*
- * function setup() {
* createCanvas(100, 100);
*
* background(200);
@@ -236,60 +289,21 @@ function files(p5, fn){
*
*
*/
- fn.loadJSON = async function (...args) {
- p5._validateParameters('loadJSON', args);
- const path = args[0];
- let callback;
- let errorCallback;
- let options;
-
- const ret = {}; // object needed for preload
- let t = 'json';
-
- // check for explicit data type argument
- for (let i = 1; i < args.length; i++) {
- const arg = args[i];
- if (typeof arg === 'string') {
- if (arg === 'json') {
- t = arg;
- }
- } else if (typeof arg === 'function') {
- if (!callback) {
- callback = arg;
- } else {
- errorCallback = arg;
- }
+ fn.loadJSON = async function (path, successCallback, errorCallback) {
+ p5._validateParameters('loadJSON', arguments);
+
+ try{
+ const { data } = await request(path, 'json');
+ if (successCallback) successCallback(data);
+ return data;
+ } catch(err) {
+ p5._friendlyFileLoadError(5, path);
+ if(errorCallback) {
+ return errorCallback(err);
+ } else {
+ throw err;
}
}
-
- await new Promise(resolve => this.httpDo(
- path,
- 'GET',
- options,
- t,
- resp => {
- for (const k in resp) {
- ret[k] = resp[k];
- }
- if (typeof callback !== 'undefined') {
- callback(resp);
- }
-
- resolve()
- },
- err => {
- // Error handling
- p5._friendlyFileLoadError(5, path);
-
- if (errorCallback) {
- errorCallback(err);
- } else {
- throw err;
- }
- }
- ));
-
- return ret;
};
/**
@@ -299,31 +313,34 @@ function files(p5, fn){
* Paths to local files should be relative, as in
* `loadStrings('assets/data.txt')`. URLs such as
* `'https://example.com/data.txt'` may be blocked due to browser security.
+ * The `path` parameter can also be defined as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
+ * object for more advanced usage.
*
* The second parameter, `successCallback`, is optional. If a function is
* passed, as in `loadStrings('assets/data.txt', handleData)`, then the
* `handleData()` function will be called once the data loads. The array
* created from the text data will be passed to `handleData()` as its only
- * argument.
+ * argument. The return value of the `handleData()` function will be used as
+ * the final return value of `loadStrings('assets/data.txt', handleData)`.
*
* The third parameter, `failureCallback`, is also optional. If a function is
* passed, as in `loadStrings('assets/data.txt', handleData, handleFailure)`,
* then the `handleFailure()` function will be called if an error occurs while
* loading. The `Error` object will be passed to `handleFailure()` as its only
- * argument.
+ * argument. The return value of the `handleFailure()` function will be used as
+ * the final return value of `loadStrings('assets/data.txt', handleData, handleFailure)`.
*
- * Note: Data can take time to load. Calling `loadStrings()` within
- * preload() ensures data loads before it's used in
- * setup() or draw() .
+ * This function returns a `Promise` and should be used in an `async` setup with
+ * `await`. See the examples for the usage syntax.
*
* @method loadStrings
- * @param {String} path path of the text file to be loaded.
+ * @param {String|Request} path path of the text file to be loaded.
* @param {Function} [successCallback] function to call once the data is
* loaded. Will be passed the array.
* @param {Function} [errorCallback] function to call if the data fails to
* load. Will be passed an `Error` event
* object.
- * @return {String[]} new array containing the loaded text.
+ * @return {Promise} new array containing the loaded text.
*
* @example
*
@@ -331,12 +348,9 @@ function files(p5, fn){
*
* let myData;
*
- * // Load the text and create an array.
- * function preload() {
- * myData = loadStrings('assets/test.txt');
- * }
+ * async function setup() {
+ * myData = await loadStrings('assets/test.txt');
*
- * function setup() {
* createCanvas(100, 100);
*
* background(200);
@@ -362,11 +376,9 @@ function files(p5, fn){
* let lastLine;
*
* // Load the text and preprocess it.
- * function preload() {
- * loadStrings('assets/test.txt', handleData);
- * }
+ * async function setup() {
+ * await loadStrings('assets/test.txt', handleData);
*
- * function setup() {
* createCanvas(100, 100);
*
* background(200);
@@ -394,11 +406,9 @@ function files(p5, fn){
* let lastLine;
*
* // Load the text and preprocess it.
- * function preload() {
- * loadStrings('assets/test.txt', handleData, handleError);
- * }
+ * async function setup() {
+ * await loadStrings('assets/test.txt', handleData, handleError);
*
- * function setup() {
* createCanvas(100, 100);
*
* background(200);
@@ -426,65 +436,23 @@ function files(p5, fn){
*
*
*/
- fn.loadStrings = async function (...args) {
- p5._validateParameters('loadStrings', args);
-
- const ret = [];
- let callback, errorCallback;
-
- for (let i = 1; i < args.length; i++) {
- const arg = args[i];
- if (typeof arg === 'function') {
- if (typeof callback === 'undefined') {
- callback = arg;
- } else if (typeof errorCallback === 'undefined') {
- errorCallback = arg;
- }
+ fn.loadStrings = async function (path, successCallback, errorCallback) {
+ p5._validateParameters('loadStrings', arguments);
+
+ try{
+ let { data } = await request(path, 'text');
+ data = data.split(/\r?\n/);
+
+ if (successCallback) successCallback(data);
+ return data;
+ } catch(err) {
+ p5._friendlyFileLoadError(3, path);
+ if(errorCallback) {
+ errorCallback(err);
+ } else {
+ throw err;
}
}
-
- await new Promise(resolve => fn.httpDo.call(
- this,
- args[0],
- 'GET',
- 'text',
- data => {
- // split lines handling mac/windows/linux endings
- const lines = data
- .replace(/\r\n/g, '\r')
- .replace(/\n/g, '\r')
- .split(/\r/);
-
- // safe insert approach which will not blow up stack when inserting
- // >100k lines, but still be faster than iterating line-by-line. based on
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Examples
- const QUANTUM = 32768;
- for (let i = 0, len = lines.length; i < len; i += QUANTUM) {
- Array.prototype.push.apply(
- ret,
- lines.slice(i, Math.min(i + QUANTUM, len))
- );
- }
-
- if (typeof callback !== 'undefined') {
- callback(ret);
- }
-
- resolve()
- },
- function (err) {
- // Error handling
- p5._friendlyFileLoadError(3, arguments[0]);
-
- if (errorCallback) {
- errorCallback(err);
- } else {
- throw err;
- }
- }
- ));
-
- return ret;
};
/**
@@ -495,17 +463,14 @@ function files(p5, fn){
* format). Table only looks for a header row if the 'header' option is
* included.
*
- * This method is asynchronous, meaning it may not finish before the next
- * line in your sketch is executed. Calling loadTable() inside preload()
- * guarantees to complete the operation before setup() and draw() are called.
- * Outside of preload() , you may supply a callback function to handle the
- * object:
+ * This function returns a `Promise` and should be used in an `async` setup with
+ * `await`. See the examples for the usage syntax.
*
* All files loaded and saved use UTF-8 encoding. This method is suitable for fetching files up to size of 64MB.
+ *
* @method loadTable
- * @param {String} filename name of the file or URL to load
- * @param {String} [extension] parse the table by comma-separated values "csv", semicolon-separated
- * values "ssv", or tab-separated values "tsv"
+ * @param {String|Request} filename name of the file or URL to load
+ * @param {String} [separator] the separator character used by the file, defaults to `','`
* @param {String} [header] "header" to indicate table has header row
* @param {Function} [callback] function to be executed after
* loadTable() completes. On success, the
@@ -514,7 +479,7 @@ function files(p5, fn){
* @param {Function} [errorCallback] function to be executed if
* there is an error, response is passed
* in as first argument
- * @return {Object} Table object containing data
+ * @return {Promise} Table object containing data
*
* @example
*
@@ -529,16 +494,9 @@ function files(p5, fn){
*
* let table;
*
- * function preload() {
- * //my table is comma separated value "csv"
- * //and has a header specifying the columns labels
- * table = loadTable('assets/mammals.csv', 'csv', 'header');
- * //the file can be remote
- * //table = loadTable("http://p5js.org/reference/assets/mammals.csv",
- * // "csv", "header");
- * }
+ * async function setup() {
+ * table = await loadTable('assets/mammals.csv', 'csv', 'header');
*
- * function setup() {
* //count the columns
* print(table.getRowCount() + ' total rows in table');
* print(table.getColumnCount() + ' total columns in table');
@@ -557,209 +515,50 @@ function files(p5, fn){
*
*
*/
- fn.loadTable = async function (path) {
- // p5._validateParameters('loadTable', arguments);
- let callback;
- let errorCallback;
- const options = [];
- let header = false;
- const ext = path.substring(path.lastIndexOf('.') + 1, path.length);
-
- let sep;
- if (ext === 'csv') {
- sep = ',';
- } else if (ext === 'ssv') {
- sep = ';';
- } else if (ext === 'tsv') {
- sep = '\t';
- }
-
- for (let i = 1; i < arguments.length; i++) {
- if (typeof arguments[i] === 'function') {
- if (typeof callback === 'undefined') {
- callback = arguments[i];
- } else if (typeof errorCallback === 'undefined') {
- errorCallback = arguments[i];
- }
- } else if (typeof arguments[i] === 'string') {
- options.push(arguments[i]);
- if (arguments[i] === 'header') {
- header = true;
- }
- if (arguments[i] === 'csv') {
- sep = ',';
- } else if (arguments[i] === 'ssv') {
- sep = ';';
- } else if (arguments[i] === 'tsv') {
- sep = '\t';
- }
+ fn.loadTable = async function (path, separator, header, successCallback, errorCallback) {
+ if(typeof arguments[arguments.length-1] === 'function'){
+ if(typeof arguments[arguments.length-2] === 'function'){
+ successCallback = arguments[arguments.length-2];
+ errorCallback = arguments[arguments.length-1];
+ }else{
+ successCallback = arguments[arguments.length-1];
}
}
- const t = new p5.Table();
-
- await new Promise(resolve => this.httpDo(
- path,
- 'GET',
- 'table',
- resp => {
- const state = {};
-
- // define constants
- const PRE_TOKEN = 0,
- MID_TOKEN = 1,
- POST_TOKEN = 2,
- POST_RECORD = 4;
-
- const QUOTE = '"',
- CR = '\r',
- LF = '\n';
-
- const records = [];
- let offset = 0;
- let currentRecord = null;
- let currentChar;
-
- const tokenBegin = () => {
- state.currentState = PRE_TOKEN;
- state.token = '';
- };
-
- const tokenEnd = () => {
- currentRecord.push(state.token);
- tokenBegin();
- };
-
- const recordBegin = () => {
- state.escaped = false;
- currentRecord = [];
- tokenBegin();
- };
-
- const recordEnd = () => {
- state.currentState = POST_RECORD;
- records.push(currentRecord);
- currentRecord = null;
- };
-
- for (; ;) {
- currentChar = resp[offset++];
-
- // EOF
- if (currentChar == null) {
- if (state.escaped) {
- throw new Error('Unclosed quote in file.');
- }
- if (currentRecord) {
- tokenEnd();
- recordEnd();
- break;
- }
- }
- if (currentRecord === null) {
- recordBegin();
- }
-
- // Handle opening quote
- if (state.currentState === PRE_TOKEN) {
- if (currentChar === QUOTE) {
- state.escaped = true;
- state.currentState = MID_TOKEN;
- continue;
- }
- state.currentState = MID_TOKEN;
- }
-
- // mid-token and escaped, look for sequences and end quote
- if (state.currentState === MID_TOKEN && state.escaped) {
- if (currentChar === QUOTE) {
- if (resp[offset] === QUOTE) {
- state.token += QUOTE;
- offset++;
- } else {
- state.escaped = false;
- state.currentState = POST_TOKEN;
- }
- } else if (currentChar === CR) {
- continue;
- } else {
- state.token += currentChar;
- }
- continue;
- }
+ if(typeof separator !== 'string') separator = ',';
+ if(typeof header === 'function') header = false;
- // fall-through: mid-token or post-token, not escaped
- if (currentChar === CR) {
- if (resp[offset] === LF) {
- offset++;
- }
- tokenEnd();
- recordEnd();
- } else if (currentChar === LF) {
- tokenEnd();
- recordEnd();
- } else if (currentChar === sep) {
- tokenEnd();
- } else if (state.currentState === MID_TOKEN) {
- state.token += currentChar;
- }
- }
-
- // set up column names
- if (header) {
- t.columns = records.shift();
- } else {
- for (let i = 0; i < records[0].length; i++) {
- t.columns[i] = 'null';
- }
- }
- let row;
- for (let i = 0; i < records.length; i++) {
- //Handles row of 'undefined' at end of some CSVs
- if (records[i].length === 1) {
- if (records[i][0] === 'undefined' || records[i][0] === '') {
- continue;
- }
- }
- row = new p5.TableRow();
- row.arr = records[i];
- row.obj = makeObject(records[i], t.columns);
- t.addRow(row);
- }
- if (typeof callback === 'function') {
- callback(t);
- }
+ try{
+ let { data } = await request(path, 'text');
+ data = data.split(/\r?\n/);
- resolve()
- },
- err => {
- // Error handling
- p5._friendlyFileLoadError(2, path);
+ let ret = new p5.Table();
- if (errorCallback) {
- errorCallback(err);
- } else {
- console.error(err);
- }
+ if(header){
+ ret.columns = data.shift().split(separator);
+ }else{
+ ret.columns = data[0].split(separator).map(() => null);
}
- ));
- return t;
- };
+ data.forEach((line) => {
+ const row = new p5.TableRow(line, separator);
+ ret.addRow(row);
+ });
- // helper function to turn a row into a JSON object
- function makeObject(row, headers) {
- headers = headers || [];
- if (typeof headers === 'undefined') {
- for (let j = 0; j < row.length; j++) {
- headers[j.toString()] = j;
+ if (successCallback) {
+ successCallback(ret);
+ } else {
+ return ret;
+ }
+ } catch(err) {
+ p5._friendlyFileLoadError(2, path);
+ if(errorCallback) {
+ return errorCallback(err);
+ } else {
+ throw err;
}
}
- return Object.fromEntries(
- headers
- .map((key, i) => [key, row[i]])
- );
- }
+ };
/**
* Loads an XML file to create a p5.XML object.
@@ -773,33 +572,36 @@ function files(p5, fn){
* The first parameter, `path`, is always a string with the path to the file.
* Paths to local files should be relative, as in
* `loadXML('assets/data.xml')`. URLs such as `'https://example.com/data.xml'`
- * may be blocked due to browser security.
+ * may be blocked due to browser security. The `path` parameter can also be defined
+ * as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
+ * object for more advanced usage.
*
* The second parameter, `successCallback`, is optional. If a function is
* passed, as in `loadXML('assets/data.xml', handleData)`, then the
* `handleData()` function will be called once the data loads. The
* p5.XML object created from the data will be passed
- * to `handleData()` as its only argument.
+ * to `handleData()` as its only argument. The return value of the `handleData()`
+ * function will be used as the final return value of `loadXML('assets/data.xml', handleData)`.
*
* The third parameter, `failureCallback`, is also optional. If a function is
* passed, as in `loadXML('assets/data.xml', handleData, handleFailure)`, then
* the `handleFailure()` function will be called if an error occurs while
* loading. The `Error` object will be passed to `handleFailure()` as its only
- * argument.
+ * argument. The return value of the `handleFailure()` function will be used as the
+ * final return value of `loadXML('assets/data.xml', handleData, handleFailure)`.
*
- * Note: Data can take time to load. Calling `loadXML()` within
- * preload() ensures data loads before it's used in
- * setup() or draw() .
+ * This function returns a `Promise` and should be used in an `async` setup with
+ * `await`. See the examples for the usage syntax.
*
* @method loadXML
- * @param {String} path path of the XML file to be loaded.
+ * @param {String|Request} path path of the XML file to be loaded.
* @param {Function} [successCallback] function to call once the data is
* loaded. Will be passed the
* p5.XML object.
* @param {Function} [errorCallback] function to call if the data fails to
* load. Will be passed an `Error` event
* object.
- * @return {p5.XML} XML data loaded into a p5.XML
+ * @return {Promise} XML data loaded into a p5.XML
* object.
*
* @example
@@ -808,11 +610,9 @@ function files(p5, fn){
* let myXML;
*
* // Load the XML and create a p5.XML object.
- * function preload() {
- * myXML = loadXML('assets/animals.xml');
- * }
+ * async function setup() {
+ * myXML = await loadXML('assets/animals.xml');
*
- * function setup() {
* createCanvas(100, 100);
*
* background(200);
@@ -850,11 +650,9 @@ function files(p5, fn){
* let lastMammal;
*
* // Load the XML and create a p5.XML object.
- * function preload() {
- * loadXML('assets/animals.xml', handleData);
- * }
+ * async function setup() {
+ * await loadXML('assets/animals.xml', handleData);
*
- * function setup() {
* createCanvas(100, 100);
*
* background(200);
@@ -886,11 +684,9 @@ function files(p5, fn){
* let lastMammal;
*
* // Load the XML and preprocess it.
- * function preload() {
- * loadXML('assets/animals.xml', handleData, handleError);
- * }
+ * async function setup() {
+ * await loadXML('assets/animals.xml', handleData, handleError);
*
- * function setup() {
* createCanvas(100, 100);
*
* background(200);
@@ -922,69 +718,44 @@ function files(p5, fn){
*
*
*/
- fn.loadXML = async function (...args) {
- const ret = new p5.XML();
- let callback, errorCallback;
-
- for (let i = 1; i < args.length; i++) {
- const arg = args[i];
- if (typeof arg === 'function') {
- if (typeof callback === 'undefined') {
- callback = arg;
- } else if (typeof errorCallback === 'undefined') {
- errorCallback = arg;
- }
+ fn.loadXML = async function (path, successCallback, errorCallback) {
+ try{
+ const parser = new DOMParser();
+
+ let { data } = await request(path, 'text');
+ const parsedDOM = parser.parseFromString(data, 'application/xml');
+ data = new p5.XML(parsedDOM);
+
+ if (successCallback) successCallback(data);
+ return data;
+ } catch(err) {
+ p5._friendlyFileLoadError(1, path);
+ if(errorCallback) {
+ errorCallback(err);
+ } else {
+ throw err;
}
}
-
- await new Promise(resolve => this.httpDo(
- args[0],
- 'GET',
- 'xml',
- xml => {
- for (const key in xml) {
- ret[key] = xml[key];
- }
- if (typeof callback !== 'undefined') {
- callback(ret);
- }
-
- resolve()
- },
- function (err) {
- // Error handling
- p5._friendlyFileLoadError(1, arguments[0]);
-
- if (errorCallback) {
- errorCallback(err);
- } else {
- throw err;
- }
- }
- ));
-
- return ret;
};
/**
* This method is suitable for fetching files up to size of 64MB.
+ *
* @method loadBytes
- * @param {String} file name of the file or URL to load
+ * @param {String|Request} file name of the file or URL to load
* @param {Function} [callback] function to be executed after loadBytes()
* completes
* @param {Function} [errorCallback] function to be executed if there
* is an error
- * @returns {Object} an object whose 'bytes' property will be the loaded buffer
+ * @returns {Promise} an object whose 'bytes' property will be the loaded buffer
*
* @example
*
* let data;
*
- * function preload() {
- * data = loadBytes('assets/mammals.xml');
- * }
+ * async function setup() {
+ * data = await loadBytes('assets/mammals.xml');
*
- * function setup() {
* for (let i = 0; i < 5; i++) {
* console.log(data.bytes[i].toString(16));
* }
@@ -992,48 +763,47 @@ function files(p5, fn){
* }
*
*/
- fn.loadBytes = async function (file, callback, errorCallback) {
- const ret = {};
-
- await new Promise(resolve => this.httpDo(
- file,
- 'GET',
- 'arrayBuffer',
- arrayBuffer => {
- ret.bytes = new Uint8Array(arrayBuffer);
-
- if (typeof callback === 'function') {
- callback(ret);
- }
-
- resolve();
- },
- err => {
- // Error handling
- p5._friendlyFileLoadError(6, file);
+ fn.loadBytes = async function (path, successCallback, errorCallback) {
+ try{
+ let { data } = await request(path, 'arrayBuffer');
+ data = new Uint8Array(data);
+ if (successCallback) successCallback(data);
+ return data;
+ } catch(err) {
+ p5._friendlyFileLoadError(6, path);
+ if(errorCallback) {
+ errorCallback(err);
+ } else {
+ throw err;
+ }
+ }
+ };
- if (errorCallback) {
- errorCallback(err);
- } else {
- throw err;
- }
+ fn.loadBlob = async function(path, successCallback, errorCallback) {
+ try{
+ const { data } = await request(path, 'blob');
+ if (successCallback) successCallback(data);
+ return data;
+ } catch(err) {
+ if(errorCallback) {
+ errorCallback(err);
+ } else {
+ throw err;
}
- ));
- return ret;
+ }
};
/**
* Method for executing an HTTP GET request. If data type is not specified,
- * p5 will try to guess based on the URL, defaulting to text. This is equivalent to
+ * it will default to `'text'`. This is equivalent to
* calling httpDo(path, 'GET')
. The 'binary' datatype will return
* a Blob object, and the 'arrayBuffer' datatype will return an ArrayBuffer
* which can be used to initialize typed arrays (such as Uint8Array).
*
* @method httpGet
- * @param {String} path name of the file or url to load
+ * @param {String|Request} path name of the file or url to load
* @param {String} [datatype] "json", "jsonp", "binary", "arrayBuffer",
* "xml", or "text"
- * @param {Object|Boolean} [data] param data passed sent with request
* @param {Function} [callback] function to be executed after
* httpGet() completes, data is passed in
* as first argument
@@ -1048,16 +818,12 @@ function files(p5, fn){
* // Examples use USGS Earthquake API:
* // https://earthquake.usgs.gov/fdsnws/event/1/#methods
* let earthquakes;
- * function preload() {
+ * async function setup() {
* // Get the most recent earthquake in the database
* let url =
'https://earthquake.usgs.gov/fdsnws/event/1/query?' +
* 'format=geojson&limit=1&orderby=time';
- * httpGet(url, 'json', function(response) {
- * // when the HTTP request completes, populate the variable that holds the
- * // earthquake data used in the visualization.
- * earthquakes = response;
- * });
+ * earthquakes = await httpGet(url, 'json');
* }
*
* function draw() {
@@ -1078,42 +844,42 @@ function files(p5, fn){
*/
/**
* @method httpGet
- * @param {String} path
- * @param {Object|Boolean} data
- * @param {Function} [callback]
- * @param {Function} [errorCallback]
- * @return {Promise}
- */
- /**
- * @method httpGet
- * @param {String} path
- * @param {Function} callback
- * @param {Function} [errorCallback]
+ * @param {String|Request} path
+ * @param {Function} callback
+ * @param {Function} [errorCallback]
* @return {Promise}
*/
- fn.httpGet = function (...args) {
- p5._validateParameters('httpGet', args);
+ fn.httpGet = async function (path, datatype='text', successCallback, errorCallback) {
+ p5._validateParameters('httpGet', arguments);
- args.splice(1, 0, 'GET');
- return fn.httpDo.apply(this, args);
+ if (typeof datatype === 'function') {
+ errorCallback = successCallback;
+ successCallback = datatype;
+ datatype = 'text';
+ }
+
+ // This is like a more primitive version of the other load functions.
+ // If the user wanted to customize more behavior, pass in Request to path.
+
+ return this.httpDo(path, 'GET', datatype, successCallback, errorCallback);
};
/**
* Method for executing an HTTP POST request. If data type is not specified,
- * p5 will try to guess based on the URL, defaulting to text. This is equivalent to
+ * it will default to `'text'`. This is equivalent to
* calling httpDo(path, 'POST')
.
*
* @method httpPost
- * @param {String} path name of the file or url to load
- * @param {String} [datatype] "json", "jsonp", "xml", or "text".
+ * @param {String|Request} path name of the file or url to load
+ * @param {Object|Boolean} [data] param data passed sent with request
+ * @param {String} [datatype] "json", "jsonp", "xml", or "text".
* If omitted, httpPost() will guess.
- * @param {Object|Boolean} [data] param data passed sent with request
- * @param {Function} [callback] function to be executed after
- * httpPost() completes, data is passed in
- * as first argument
- * @param {Function} [errorCallback] function to be executed if
- * there is an error, response is passed
- * in as first argument
+ * @param {Function} [callback] function to be executed after
+ * httpPost() completes, data is passed in
+ * as first argument
+ * @param {Function} [errorCallback] function to be executed if
+ * there is an error, response is passed
+ * in as first argument
* @return {Promise} A promise that resolves with the data when the operation
* completes successfully or rejects with the error after
* one occurs.
@@ -1167,46 +933,95 @@ function files(p5, fn){
*/
/**
* @method httpPost
- * @param {String} path
- * @param {Object|Boolean} data
- * @param {Function} [callback]
- * @param {Function} [errorCallback]
+ * @param {String|Request} path
+ * @param {Object|Boolean} data
+ * @param {Function} [callback]
+ * @param {Function} [errorCallback]
* @return {Promise}
*/
/**
* @method httpPost
- * @param {String} path
- * @param {Function} callback
- * @param {Function} [errorCallback]
+ * @param {String|Request} path
+ * @param {Function} [callback]
+ * @param {Function} [errorCallback]
* @return {Promise}
*/
- fn.httpPost = function (...args) {
- p5._validateParameters('httpPost', args);
+ fn.httpPost = async function (path, data, datatype='text', successCallback, errorCallback) {
+ p5._validateParameters('httpPost', arguments);
+
+ // This behave similarly to httpGet and additional options should be passed
+ // as a `Request`` to path. Both method and body will be overridden.
+ // Will try to infer correct Content-Type for given data.
+
+ if (typeof data === 'function') {
+ // Assume both data and datatype are functions as data should not be function
+ successCallback = data;
+ errorCallback = datatype;
+ data = undefined;
+ datatype = 'text';
+
+ } else if (typeof datatype === 'function') {
+ // Data is provided but not datatype\
+ errorCallback = successCallback;
+ successCallback = datatype;
+ datatype = 'text';
+ }
- args.splice(1, 0, 'POST');
- return fn.httpDo.apply(this, args);
+ let reqData = data;
+ let contentType = 'text/plain';
+ // Normalize data
+ if(data instanceof p5.XML) {
+ reqData = data.serialize();
+ contentType = 'application/xml';
+
+ } else if(data instanceof p5.Image) {
+ reqData = await data.toBlob();
+ contentType = 'image/png';
+
+ } else if (typeof data === 'object') {
+ reqData = JSON.stringify(data);
+ contentType = 'application/json';
+ }
+
+ const requestOptions = {
+ method: 'POST',
+ body: reqData,
+ headers: {
+ 'Content-Type': contentType
+ }
+ };
+
+ if (reqData) {
+ requestOptions.body = reqData;
+ }
+
+ const req = new Request(path, requestOptions);
+
+ return this.httpDo(req, 'POST', datatype, successCallback, errorCallback);
};
/**
* Method for executing an HTTP request. If data type is not specified,
- * p5 will try to guess based on the URL, defaulting to text.
- * For more advanced use, you may also pass in the path as the first argument
- * and a object as the second argument, the signature follows the one specified
- * in the Fetch API specification.
+ * it will default to `'text'`.
+ *
+ * This function is meant for more advanced usage of HTTP requests in p5.js. It is
+ * best used when a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
+ * object is passed to the `path` parameter.
+ *
* This method is suitable for fetching files up to size of 64MB when "GET" is used.
*
* @method httpDo
- * @param {String} path name of the file or url to load
- * @param {String} [method] either "GET", "POST", or "PUT",
- * defaults to "GET"
- * @param {String} [datatype] "json", "jsonp", "xml", or "text"
- * @param {Object} [data] param data passed sent with request
- * @param {Function} [callback] function to be executed after
- * httpGet() completes, data is passed in
- * as first argument
- * @param {Function} [errorCallback] function to be executed if
- * there is an error, response is passed
- * in as first argument
+ * @param {String|Request} path name of the file or url to load
+ * @param {String} [method] either "GET", "POST", "PUT", "DELETE",
+ * or other HTTP request methods
+ * @param {String} [datatype] "json", "jsonp", "xml", or "text"
+ * @param {Object} [data] param data passed sent with request
+ * @param {Function} [callback] function to be executed after
+ * httpGet() completes, data is passed in
+ * as first argument
+ * @param {Function} [errorCallback] function to be executed if
+ * there is an error, response is passed
+ * in as first argument
* @return {Promise} A promise that resolves with the data when the operation
* completes successfully or rejects with the error after
* one occurs.
@@ -1221,7 +1036,7 @@ function files(p5, fn){
* let earthquakes;
* let eqFeatureIndex = 0;
*
- * function preload() {
+ * function setup() {
* let url = 'https://earthquake.usgs.gov/fdsnws/event/1/query?format=geojson';
* httpDo(
* url,
@@ -1260,135 +1075,67 @@ function files(p5, fn){
*/
/**
* @method httpDo
- * @param {String} path
- * @param {Object} options Request object options as documented in the
- * "fetch" API
- * reference
- * @param {Function} [callback]
- * @param {Function} [errorCallback]
+ * @param {String|Request} path
+ * @param {Function} [callback]
+ * @param {Function} [errorCallback]
* @return {Promise}
*/
- fn.httpDo = function (...args) {
- let type;
- let callback;
- let errorCallback;
- let request;
- let promise;
- let cbCount = 0;
- let contentType = 'text/plain';
- // Trim the callbacks off the end to get an idea of how many arguments are passed
- for (let i = args.length - 1; i > 0; i--) {
- if (typeof args[i] === 'function') {
- cbCount++;
- } else {
- break;
- }
+ fn.httpDo = async function (path, method, datatype, successCallback, errorCallback) {
+ // This behave similarly to httpGet but even more primitive. The user
+ // will most likely want to pass in a Request to path, the only convenience
+ // is that datatype will be taken into account to parse the response.
+
+ if(typeof datatype === 'function'){
+ errorCallback = successCallback;
+ successCallback = datatype;
+ datatype = undefined;
}
- // The number of arguments minus callbacks
- const argsCount = args.length - cbCount;
- const path = args[0];
- if (
- argsCount === 2 &&
- typeof path === 'string' &&
- typeof args[1] === 'object'
- ) {
- // Intended for more advanced use, pass in Request parameters directly
- request = new Request(path, args[1]);
- callback = args[2];
- errorCallback = args[3];
- } else {
- // Provided with arguments
- let method = 'GET';
- let data;
- for (let j = 1; j < args.length; j++) {
- const a = args[j];
- if (typeof a === 'string') {
- if (a === 'GET' || a === 'POST' || a === 'PUT' || a === 'DELETE') {
- method = a;
- } else if (
- a === 'json' ||
- a === 'binary' ||
- a === 'arrayBuffer' ||
- a === 'xml' ||
- a === 'text' ||
- a === 'table'
- ) {
- type = a;
- } else {
- data = a;
- }
- } else if (typeof a === 'number') {
- data = a.toString();
- } else if (typeof a === 'object') {
- if (a instanceof p5.XML) {
- data = a.serialize();
- contentType = 'application/xml';
- } else {
- data = JSON.stringify(a);
- contentType = 'application/json';
- }
- } else if (typeof a === 'function') {
- if (!callback) {
- callback = a;
- } else {
- errorCallback = a;
- }
- }
+ // Try to infer data type if it is defined
+ if(!datatype){
+ const extension = typeof path === 'string' ?
+ path.split(".").pop() :
+ path.url.split(".").pop();
+ switch(extension) {
+ case 'json':
+ datatype = 'json';
+ break;
+
+ case 'jpg':
+ case 'jpeg':
+ case 'png':
+ case 'webp':
+ case 'gif':
+ datatype = 'blob';
+ break;
+
+ case 'xml':
+ // NOTE: still need to normalize type handling/mapping
+ // datatype = 'xml';
+ case 'txt':
+ default:
+ datatype = 'text';
}
+ }
- let headers =
- method === 'GET'
- ? new Headers()
- : new Headers({ 'Content-Type': contentType });
+ const req = new Request(path, {
+ method
+ });
- request = new Request(path, {
- method,
- mode: 'cors',
- body: data,
- headers
- });
- }
- // do some sort of smart type checking
- if (!type) {
- if (path.includes('json')) {
- type = 'json';
- } else if (path.includes('xml')) {
- type = 'xml';
+ try{
+ const { data } = await request(req, datatype);
+ if (successCallback) {
+ return successCallback(data);
} else {
- type = 'text';
+ return data;
}
- }
-
- promise = fetch(request);
- promise = promise.then(res => {
- if (!res.ok) {
- const err = new Error(res.body);
- err.status = res.status;
- err.ok = false;
- throw err;
+ } catch(err) {
+ if(errorCallback) {
+ return errorCallback(err);
} else {
- switch (type) {
- case 'json':
- return res.json();
- case 'binary':
- return res.blob();
- case 'arrayBuffer':
- return res.arrayBuffer();
- case 'xml':
- return res.text().then(text => {
- const parser = new DOMParser();
- const xml = parser.parseFromString(text, 'text/xml');
- return new p5.XML(xml.documentElement);
- });
- default:
- return res.text();
- }
+ throw err;
}
- });
- promise.then(callback || (() => { }));
- promise.catch(errorCallback || console.error);
- return promise;
+ }
};
/**
@@ -1396,9 +1143,6 @@ function files(p5, fn){
* @submodule Output
* @for p5
*/
-
- window.URL = window.URL || window.webkitURL;
-
// private array of p5.PrintWriter objects
fn._pWriters = [];
@@ -1889,8 +1633,8 @@ function files(p5, fn){
* with line breaks.`);
*
*/
-
fn.save = function (object, _filename, _options) {
+ // TODO: parameters is not used correctly
// parse the arguments and figure out which things we are saving
const args = arguments;
// =================================================
@@ -1901,15 +1645,17 @@ function files(p5, fn){
if (args.length === 0) {
fn.saveCanvas(cnv);
return;
- } else if (args[0] instanceof p5.Renderer || args[0] instanceof p5.Graphics) {
- // otherwise, parse the arguments
+ } else if (args[0] instanceof Renderer || args[0] instanceof Graphics) {
+ // otherwise, parse the arguments
// if first param is a p5Graphics, then saveCanvas
fn.saveCanvas(args[0].canvas, args[1], args[2]);
return;
+
} else if (args.length === 1 && typeof args[0] === 'string') {
// if 1st param is String and only one arg, assume it is canvas filename
fn.saveCanvas(cnv, args[0]);
+
} else {
// =================================================
// OPTION 2: extension clarifies saveStrings vs. saveJSON
@@ -2062,10 +1808,10 @@ function files(p5, fn){
*
*
*/
- fn.saveJSON = function (json, filename, opt) {
+ fn.saveJSON = function (json, filename, optimize) {
p5._validateParameters('saveJSON', arguments);
let stringify;
- if (opt) {
+ if (optimize) {
stringify = JSON.stringify(json);
} else {
stringify = JSON.stringify(json, undefined, 2);
@@ -2073,9 +1819,6 @@ function files(p5, fn){
this.saveStrings(stringify.split('\n'), filename, 'json');
};
- fn.saveJSONObject = fn.saveJSON;
- fn.saveJSONArray = fn.saveJSON;
-
/**
* Saves an `Array` of `String`s to a file, one per line.
*
@@ -2212,9 +1955,9 @@ function files(p5, fn){
fn.saveStrings = function (list, filename, extension, isCRLF) {
p5._validateParameters('saveStrings', arguments);
const ext = extension || 'txt';
- const pWriter = this.createWriter(filename, ext);
- for (let i = 0; i < list.length; i++) {
- isCRLF ? pWriter.write(list[i] + '\r\n') : pWriter.write(list[i] + '\n');
+ const pWriter = new p5.PrintWriter(filename, ext);
+ for (let item of list) {
+ isCRLF ? pWriter.write(item + '\r\n') : pWriter.write(item + '\n');
}
pWriter.close();
pWriter.clear();
@@ -2276,6 +2019,7 @@ function files(p5, fn){
let ext;
if (options === undefined) {
ext = filename.substring(filename.lastIndexOf('.') + 1, filename.length);
+ if(ext === filename) ext = 'csv';
} else {
ext = options;
}
@@ -2403,34 +2147,13 @@ function files(p5, fn){
fn.downloadFile = function (data, fName, extension) {
const fx = _checkFileExtension(fName, extension);
const filename = fx[0];
+ let saveData = data;
- if (data instanceof Blob) {
- fileSaver.saveAs(data, filename);
- return;
+ if (!(saveData instanceof Blob)) {
+ saveData = new Blob([data]);
}
- const a = document.createElement('a');
- a.href = data;
- a.download = filename;
-
- // Firefox requires the link to be added to the DOM before click()
- a.onclick = e => {
- destroyClickedElement(e);
- e.stopPropagation();
- };
-
- a.style.display = 'none';
- document.body.appendChild(a);
-
- // Safari will open this file in the same page as a confusing Blob.
- if (fn._isSafari()) {
- let aText = 'Hello, Safari user! To download this file...\n';
- aText += '1. Go to File --> Save As.\n';
- aText += '2. Choose "Page Source" as the Format.\n';
- aText += `3. Name it with this extension: ."${fx[1]}"`;
- alert(aText);
- }
- a.click();
+ fileSaver.saveAs(saveData, filename);
};
/**
diff --git a/src/io/p5.TableRow.js b/src/io/p5.TableRow.js
index eb171e2766..85bbf6cc61 100644
--- a/src/io/p5.TableRow.js
+++ b/src/io/p5.TableRow.js
@@ -33,44 +33,44 @@ function tableRow(p5, fn){
}
/**
- * Stores a value in the TableRow's specified column.
- * The column may be specified by either its ID or title.
- *
- * @method set
- * @param {String|Integer} column Column ID (Number)
- * or Title (String)
- * @param {String|Number} value The value to be stored
- *
- * @example
- *
- * // Given the CSV file "mammals.csv" in the project's "assets" folder:
- * //
- * // id,species,name
- * // 0,Capra hircus,Goat
- * // 1,Panthera pardus,Leopard
- * // 2,Equus zebra,Zebra
- *
- * let table;
- *
- * function preload() {
- * //my table is comma separated value "csv"
- * //and has a header specifying the columns labels
- * table = loadTable('assets/mammals.csv', 'csv', 'header');
- * }
- *
- * function setup() {
- * let rows = table.getRows();
- * for (let r = 0; r < rows.length; r++) {
- * rows[r].set('name', 'Unicorn');
- * }
- *
- * //print the results
- * print(table.getArray());
- *
- * describe('no image displayed');
- * }
- *
- */
+ * Stores a value in the TableRow's specified column.
+ * The column may be specified by either its ID or title.
+ *
+ * @method set
+ * @param {String|Integer} column Column ID (Number)
+ * or Title (String)
+ * @param {String|Number} value The value to be stored
+ *
+ * @example
+ *
+ * // Given the CSV file "mammals.csv" in the project's "assets" folder:
+ * //
+ * // id,species,name
+ * // 0,Capra hircus,Goat
+ * // 1,Panthera pardus,Leopard
+ * // 2,Equus zebra,Zebra
+ *
+ * let table;
+ *
+ * function preload() {
+ * //my table is comma separated value "csv"
+ * //and has a header specifying the columns labels
+ * table = loadTable('assets/mammals.csv', 'csv', 'header');
+ * }
+ *
+ * function setup() {
+ * let rows = table.getRows();
+ * for (let r = 0; r < rows.length; r++) {
+ * rows[r].set('name', 'Unicorn');
+ * }
+ *
+ * //print the results
+ * print(table.getArray());
+ *
+ * describe('no image displayed');
+ * }
+ *
+ */
set(column, value) {
// if typeof column is string, use .obj
if (typeof column === 'string') {
@@ -94,131 +94,131 @@ function tableRow(p5, fn){
}
/**
- * Stores a Float value in the TableRow's specified column.
- * The column may be specified by either its ID or title.
- *
- * @method setNum
- * @param {String|Integer} column Column ID (Number)
- * or Title (String)
- * @param {Number|String} value The value to be stored
- * as a Float
- * @example
- *
- * // Given the CSV file "mammals.csv" in the project's "assets" folder:
- * //
- * // id,species,name
- * // 0,Capra hircus,Goat
- * // 1,Panthera pardus,Leopard
- * // 2,Equus zebra,Zebra
- *
- * let table;
- *
- * function preload() {
- * //my table is comma separated value "csv"
- * //and has a header specifying the columns labels
- * table = loadTable('assets/mammals.csv', 'csv', 'header');
- * }
- *
- * function setup() {
- * let rows = table.getRows();
- * for (let r = 0; r < rows.length; r++) {
- * rows[r].setNum('id', r + 10);
- * }
- *
- * print(table.getArray());
- *
- * describe('no image displayed');
- * }
- *
- */
+ * Stores a Float value in the TableRow's specified column.
+ * The column may be specified by either its ID or title.
+ *
+ * @method setNum
+ * @param {String|Integer} column Column ID (Number)
+ * or Title (String)
+ * @param {Number|String} value The value to be stored
+ * as a Float
+ * @example
+ *
+ * // Given the CSV file "mammals.csv" in the project's "assets" folder:
+ * //
+ * // id,species,name
+ * // 0,Capra hircus,Goat
+ * // 1,Panthera pardus,Leopard
+ * // 2,Equus zebra,Zebra
+ *
+ * let table;
+ *
+ * function preload() {
+ * //my table is comma separated value "csv"
+ * //and has a header specifying the columns labels
+ * table = loadTable('assets/mammals.csv', 'csv', 'header');
+ * }
+ *
+ * function setup() {
+ * let rows = table.getRows();
+ * for (let r = 0; r < rows.length; r++) {
+ * rows[r].setNum('id', r + 10);
+ * }
+ *
+ * print(table.getArray());
+ *
+ * describe('no image displayed');
+ * }
+ *
+ */
setNum(column, value) {
const floatVal = parseFloat(value);
this.set(column, floatVal);
}
/**
- * Stores a String value in the TableRow's specified column.
- * The column may be specified by either its ID or title.
- *
- * @method setString
- * @param {String|Integer} column Column ID (Number)
- * or Title (String)
- * @param {String|Number|Boolean|Object} value The value to be stored
- * as a String
- * @example
- *
- * // Given the CSV file "mammals.csv" in the project's "assets" folder:
- * //
- * // id,species,name
- * // 0,Capra hircus,Goat
- * // 1,Panthera pardus,Leopard
- * // 2,Equus zebra,Zebra
- *
- * let table;
- *
- * function preload() {
- * //my table is comma separated value "csv"
- * //and has a header specifying the columns labels
- * table = loadTable('assets/mammals.csv', 'csv', 'header');
- * }
- *
- * function setup() {
- * let rows = table.getRows();
- * for (let r = 0; r < rows.length; r++) {
- * let name = rows[r].getString('name');
- * rows[r].setString('name', 'A ' + name + ' named George');
- * }
- *
- * print(table.getArray());
- *
- * describe('no image displayed');
- * }
- *
- */
+ * Stores a String value in the TableRow's specified column.
+ * The column may be specified by either its ID or title.
+ *
+ * @method setString
+ * @param {String|Integer} column Column ID (Number)
+ * or Title (String)
+ * @param {String|Number|Boolean|Object} value The value to be stored
+ * as a String
+ * @example
+ *
+ * // Given the CSV file "mammals.csv" in the project's "assets" folder:
+ * //
+ * // id,species,name
+ * // 0,Capra hircus,Goat
+ * // 1,Panthera pardus,Leopard
+ * // 2,Equus zebra,Zebra
+ *
+ * let table;
+ *
+ * function preload() {
+ * //my table is comma separated value "csv"
+ * //and has a header specifying the columns labels
+ * table = loadTable('assets/mammals.csv', 'csv', 'header');
+ * }
+ *
+ * function setup() {
+ * let rows = table.getRows();
+ * for (let r = 0; r < rows.length; r++) {
+ * let name = rows[r].getString('name');
+ * rows[r].setString('name', 'A ' + name + ' named George');
+ * }
+ *
+ * print(table.getArray());
+ *
+ * describe('no image displayed');
+ * }
+ *
+ */
setString(column, value) {
const stringVal = value.toString();
this.set(column, stringVal);
}
/**
- * Retrieves a value from the TableRow's specified column.
- * The column may be specified by either its ID or title.
- *
- * @method get
- * @param {String|Integer} column columnName (string) or
- * ID (number)
- * @return {String|Number}
- *
- * @example
- *
- * // Given the CSV file "mammals.csv" in the project's "assets" folder:
- * //
- * // id,species,name
- * // 0,Capra hircus,Goat
- * // 1,Panthera pardus,Leopard
- * // 2,Equus zebra,Zebra
- *
- * let table;
- *
- * function preload() {
- * //my table is comma separated value "csv"
- * //and has a header specifying the columns labels
- * table = loadTable('assets/mammals.csv', 'csv', 'header');
- * }
- *
- * function setup() {
- * let names = [];
- * let rows = table.getRows();
- * for (let r = 0; r < rows.length; r++) {
- * names.push(rows[r].get('name'));
- * }
- *
- * print(names);
- *
- * describe('no image displayed');
- * }
- *
- */
+ * Retrieves a value from the TableRow's specified column.
+ * The column may be specified by either its ID or title.
+ *
+ * @method get
+ * @param {String|Integer} column columnName (string) or
+ * ID (number)
+ * @return {String|Number}
+ *
+ * @example
+ *
+ * // Given the CSV file "mammals.csv" in the project's "assets" folder:
+ * //
+ * // id,species,name
+ * // 0,Capra hircus,Goat
+ * // 1,Panthera pardus,Leopard
+ * // 2,Equus zebra,Zebra
+ *
+ * let table;
+ *
+ * function preload() {
+ * //my table is comma separated value "csv"
+ * //and has a header specifying the columns labels
+ * table = loadTable('assets/mammals.csv', 'csv', 'header');
+ * }
+ *
+ * function setup() {
+ * let names = [];
+ * let rows = table.getRows();
+ * for (let r = 0; r < rows.length; r++) {
+ * names.push(rows[r].get('name'));
+ * }
+ *
+ * print(names);
+ *
+ * describe('no image displayed');
+ * }
+ *
+ */
get(column) {
if (typeof column === 'string') {
return this.obj[column];
@@ -228,45 +228,45 @@ function tableRow(p5, fn){
}
/**
- * Retrieves a Float value from the TableRow's specified
- * column. The column may be specified by either its ID or
- * title.
- *
- * @method getNum
- * @param {String|Integer} column columnName (string) or
- * ID (number)
- * @return {Number} Float Floating point number
- * @example
- *
- * // Given the CSV file "mammals.csv" in the project's "assets" folder:
- * //
- * // id,species,name
- * // 0,Capra hircus,Goat
- * // 1,Panthera pardus,Leopard
- * // 2,Equus zebra,Zebra
- *
- * let table;
- *
- * function preload() {
- * //my table is comma separated value "csv"
- * //and has a header specifying the columns labels
- * table = loadTable('assets/mammals.csv', 'csv', 'header');
- * }
- *
- * function setup() {
- * let rows = table.getRows();
- * let minId = Infinity;
- * let maxId = -Infinity;
- * for (let r = 0; r < rows.length; r++) {
- * let id = rows[r].getNum('id');
- * minId = min(minId, id);
- * maxId = min(maxId, id);
- * }
- * print('minimum id = ' + minId + ', maximum id = ' + maxId);
- * describe('no image displayed');
- * }
- *
- */
+ * Retrieves a Float value from the TableRow's specified
+ * column. The column may be specified by either its ID or
+ * title.
+ *
+ * @method getNum
+ * @param {String|Integer} column columnName (string) or
+ * ID (number)
+ * @return {Number} Float Floating point number
+ * @example
+ *
+ * // Given the CSV file "mammals.csv" in the project's "assets" folder:
+ * //
+ * // id,species,name
+ * // 0,Capra hircus,Goat
+ * // 1,Panthera pardus,Leopard
+ * // 2,Equus zebra,Zebra
+ *
+ * let table;
+ *
+ * function preload() {
+ * //my table is comma separated value "csv"
+ * //and has a header specifying the columns labels
+ * table = loadTable('assets/mammals.csv', 'csv', 'header');
+ * }
+ *
+ * function setup() {
+ * let rows = table.getRows();
+ * let minId = Infinity;
+ * let maxId = -Infinity;
+ * for (let r = 0; r < rows.length; r++) {
+ * let id = rows[r].getNum('id');
+ * minId = min(minId, id);
+ * maxId = min(maxId, id);
+ * }
+ * print('minimum id = ' + minId + ', maximum id = ' + maxId);
+ * describe('no image displayed');
+ * }
+ *
+ */
getNum(column) {
let ret;
if (typeof column === 'string') {
@@ -282,47 +282,47 @@ function tableRow(p5, fn){
}
/**
- * Retrieves an String value from the TableRow's specified
- * column. The column may be specified by either its ID or
- * title.
- *
- * @method getString
- * @param {String|Integer} column columnName (string) or
- * ID (number)
- * @return {String} String
- * @example
- *
- * // Given the CSV file "mammals.csv" in the project's "assets" folder:
- * //
- * // id,species,name
- * // 0,Capra hircus,Goat
- * // 1,Panthera pardus,Leopard
- * // 2,Equus zebra,Zebra
- *
- * let table;
- *
- * function preload() {
- * //my table is comma separated value "csv"
- * //and has a header specifying the columns labels
- * table = loadTable('assets/mammals.csv', 'csv', 'header');
- * }
- *
- * function setup() {
- * let rows = table.getRows();
- * let longest = '';
- * for (let r = 0; r < rows.length; r++) {
- * let species = rows[r].getString('species');
- * if (longest.length < species.length) {
- * longest = species;
- * }
- * }
- *
- * print('longest: ' + longest);
- *
- * describe('no image displayed');
- * }
- *
- */
+ * Retrieves an String value from the TableRow's specified
+ * column. The column may be specified by either its ID or
+ * title.
+ *
+ * @method getString
+ * @param {String|Integer} column columnName (string) or
+ * ID (number)
+ * @return {String} String
+ * @example
+ *
+ * // Given the CSV file "mammals.csv" in the project's "assets" folder:
+ * //
+ * // id,species,name
+ * // 0,Capra hircus,Goat
+ * // 1,Panthera pardus,Leopard
+ * // 2,Equus zebra,Zebra
+ *
+ * let table;
+ *
+ * function preload() {
+ * //my table is comma separated value "csv"
+ * //and has a header specifying the columns labels
+ * table = loadTable('assets/mammals.csv', 'csv', 'header');
+ * }
+ *
+ * function setup() {
+ * let rows = table.getRows();
+ * let longest = '';
+ * for (let r = 0; r < rows.length; r++) {
+ * let species = rows[r].getString('species');
+ * if (longest.length < species.length) {
+ * longest = species;
+ * }
+ * }
+ *
+ * print('longest: ' + longest);
+ *
+ * describe('no image displayed');
+ * }
+ *
+ */
getString(column) {
if (typeof column === 'string') {
return this.obj[column].toString();
diff --git a/src/webgl/loading.js b/src/webgl/loading.js
index 4163cb04c7..66270dbb0a 100755
--- a/src/webgl/loading.js
+++ b/src/webgl/loading.js
@@ -6,8 +6,18 @@
* @requires p5.Geometry
*/
-import { Geometry } from "./p5.Geometry";
-import { Vector } from "../math/p5.Vector";
+import { Geometry } from './p5.Geometry';
+import { Vector } from '../math/p5.Vector';
+import { request } from '../io/files';
+
+async function fileExists(url) {
+ try {
+ const response = await fetch(url, { method: 'HEAD' });
+ return response.ok;
+ } catch (error) {
+ return false;
+ }
+}
function loading(p5, fn){
/**
@@ -21,10 +31,11 @@ function loading(p5, fn){
* There are three ways to call `loadModel()` with optional parameters to help
* process the model.
*
- * The first parameter, `path`, is always a `String` with the path to the
- * file. Paths to local files should be relative, as in
- * `loadModel('assets/model.obj')`. URLs such as
- * `'https://example.com/model.obj'` may be blocked due to browser security.
+ * The first parameter, `path`, is a `String` with the path to the file. Paths
+ * to local files should be relative, as in `loadModel('assets/model.obj')`.
+ * URLs such as `'https://example.com/model.obj'` may be blocked due to browser
+ * security. The `path` parameter can also be defined as a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request)
+ * object for more advanced usage.
*
* The first way to call `loadModel()` has three optional parameters after the
* file path. The first optional parameter, `successCallback`, is a function
@@ -75,21 +86,20 @@ function loading(p5, fn){
* loadModel('assets/model.obj', options);
* ```
*
- * Models can take time to load. Calling `loadModel()` in
- * preload() ensures models load before they're
- * used in setup() or draw() .
+ * This function returns a `Promise` and should be used in an `async` setup with
+ * `await`. See the examples for the usage syntax.
*
* Note: There’s no support for colored STL files. STL files with color will
* be rendered without color.
*
* @method loadModel
- * @param {String} path path of the model to be loaded.
+ * @param {String|Request} path path of the model to be loaded.
* @param {Boolean} normalize if `true`, scale the model to fit the canvas.
* @param {function(p5.Geometry)} [successCallback] function to call once the model is loaded. Will be passed
* the p5.Geometry object.
* @param {function(Event)} [failureCallback] function to call if the model fails to load. Will be passed an `Error` event object.
* @param {String} [fileType] model’s file extension. Either `'.obj'` or `'.stl'`.
- * @return {p5.Geometry} the p5.Geometry object
+ * @return {Promise} the p5.Geometry object
*
* @example
*
@@ -99,11 +109,9 @@ function loading(p5, fn){
* let shape;
*
* // Load the file and create a p5.Geometry object.
- * function preload() {
- * shape = loadModel('assets/teapot.obj');
- * }
+ * async function setup() {
+ * shape = await loadModel('assets/teapot.obj');
*
- * function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white teapot drawn against a gray background.');
@@ -129,11 +137,9 @@ function loading(p5, fn){
*
* // Load the file and create a p5.Geometry object.
* // Normalize the geometry's size to fit the canvas.
- * function preload() {
- * shape = loadModel('assets/teapot.obj', true);
- * }
+ * async function setup() {
+ * shape = await loadModel('assets/teapot.obj', true);
*
- * function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white teapot drawn against a gray background.');
@@ -158,11 +164,9 @@ function loading(p5, fn){
* let shape;
*
* // Load the file and create a p5.Geometry object.
- * function preload() {
+ * function setup() {
* loadModel('assets/teapot.obj', true, handleModel);
- * }
*
- * function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white teapot drawn against a gray background.');
@@ -194,11 +198,9 @@ function loading(p5, fn){
* let shape;
*
* // Load the file and create a p5.Geometry object.
- * function preload() {
+ * function setup() {
* loadModel('assets/wrong.obj', true, handleModel, handleError);
- * }
*
- * function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white teapot drawn against a gray background.');
@@ -235,11 +237,9 @@ function loading(p5, fn){
* let shape;
*
* // Load the file and create a p5.Geometry object.
- * function preload() {
+ * function setup() {
* loadModel('assets/teapot.obj', true, handleModel, handleError, '.obj');
- * }
*
- * function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white teapot drawn against a gray background.');
@@ -282,11 +282,9 @@ function loading(p5, fn){
* };
*
* // Load the file and create a p5.Geometry object.
- * function preload() {
+ * function setup() {
* loadModel('assets/teapot.obj', options);
- * }
*
- * function setup() {
* createCanvas(100, 100, WEBGL);
*
* describe('A white teapot drawn against a gray background.');
@@ -318,15 +316,15 @@ function loading(p5, fn){
*/
/**
* @method loadModel
- * @param {String} path
+ * @param {String|Request} path
* @param {function(p5.Geometry)} [successCallback]
* @param {function(Event)} [failureCallback]
* @param {String} [fileType]
- * @return {p5.Geometry} new
p5.Geometry object.
+ * @return {Promise
} new p5.Geometry object.
*/
/**
* @method loadModel
- * @param {String} path
+ * @param {String|Request} path
* @param {Object} [options] loading options.
* @param {function(p5.Geometry)} [options.successCallback]
* @param {function(Event)} [options.failureCallback]
@@ -334,48 +332,62 @@ function loading(p5, fn){
* @param {Boolean} [options.normalize]
* @param {Boolean} [options.flipU]
* @param {Boolean} [options.flipV]
- * @return {p5.Geometry} new p5.Geometry object.
+ * @return {Promise} new p5.Geometry object.
*/
- fn.loadModel = async function (path, options) {
+ fn.loadModel = async function (path, fileType, normalize, successCallback, failureCallback) {
p5._validateParameters('loadModel', arguments);
- let normalize = false;
- let successCallback;
- let failureCallback;
+
let flipU = false;
let flipV = false;
- let fileType = path.slice(-4);
- if (options && typeof options === 'object') {
- normalize = options.normalize || false;
- successCallback = options.successCallback;
- failureCallback = options.failureCallback;
- fileType = options.fileType || fileType;
- flipU = options.flipU || false;
- flipV = options.flipV || false;
- } else if (typeof options === 'boolean') {
- normalize = options;
- successCallback = arguments[2];
- failureCallback = arguments[3];
- if (typeof arguments[4] !== 'undefined') {
- fileType = arguments[4];
- }
+
+ if (typeof fileType === 'object') {
+ // Passing in options object
+ normalize = fileType.normalize || false;
+ successCallback = fileType.successCallback;
+ failureCallback = fileType.failureCallback;
+ fileType = fileType.fileType || fileType;
+ flipU = fileType.flipU || false;
+ flipV = fileType.flipV || false;
+
} else {
- successCallback = typeof arguments[1] === 'function' ? arguments[1] : undefined;
- failureCallback = arguments[2];
- if (typeof arguments[3] !== 'undefined') {
- fileType = arguments[3];
+ // Passing in individual parameters
+ if(typeof arguments[arguments.length-1] === 'function'){
+ if(typeof arguments[arguments.length-2] === 'function'){
+ successCallback = arguments[arguments.length-2];
+ failureCallback = arguments[arguments.length-1];
+ }else{
+ successCallback = arguments[arguments.length-1];
+ }
+ }
+
+ if (typeof fileType === 'string') {
+ if(typeof normalize !== 'boolean') normalize = false;
+
+ } else if (typeof fileType === 'boolean') {
+ normalize = fileType;
+ fileType = path.slice(-4);
+
+ } else {
+ fileType = path.slice(-4);
+ normalize = false;
}
}
+ if (fileType.toLowerCase() !== '.obj' && fileType.toLowerCase() !== '.stl') {
+ fileType = '.obj';
+ }
+
const model = new Geometry();
model.gid = `${path}|${normalize}`;
- const self = this;
async function getMaterials(lines) {
const parsedMaterialPromises = [];
- for (let i = 0; i < lines.length; i++) {
- const mtllibMatch = lines[i].match(/^mtllib (.+)/);
+ for (let line of lines) {
+ const mtllibMatch = line.match(/^mtllib (.+)/);
+
if (mtllibMatch) {
+ // Object has material
let mtlPath = '';
const mtlFilename = mtllibMatch[1];
const objPathParts = path.split('/');
@@ -386,10 +398,11 @@ function loading(p5, fn){
} else {
mtlPath = mtlFilename;
}
+
parsedMaterialPromises.push(
fileExists(mtlPath).then(exists => {
if (exists) {
- return parseMtl(self, mtlPath);
+ return parseMtl(mtlPath);
} else {
console.warn(`MTL file not found or error in parsing; proceeding without materials: ${mtlPath}`);
return {};
@@ -402,6 +415,7 @@ function loading(p5, fn){
);
}
}
+
try {
const parsedMaterials = await Promise.all(parsedMaterialPromises);
const materials = Object.assign({}, ...parsedMaterials);
@@ -411,135 +425,104 @@ function loading(p5, fn){
}
}
+ try{
+ if (fileType.match(/\.stl$/i)) {
+ const { data } = await request(path, 'arrayBuffer');
+ parseSTL(model, data);
- async function fileExists(url) {
- try {
- const response = await fetch(url, { method: 'HEAD' });
- return response.ok;
- } catch (error) {
- return false;
- }
- }
- if (fileType.match(/\.stl$/i)) {
- await new Promise(resolve => this.httpDo(
- path,
- 'GET',
- 'arrayBuffer',
- arrayBuffer => {
- parseSTL(model, arrayBuffer);
-
- if (normalize) {
- model.normalize();
- }
+ if (normalize) {
+ model.normalize();
+ }
- if (flipU) {
- model.flipU();
- }
+ if (flipU) {
+ model.flipU();
+ }
- if (flipV) {
- model.flipV();
- }
+ if (flipV) {
+ model.flipV();
+ }
- resolve();
- if (typeof successCallback === 'function') {
- successCallback(model);
- }
- },
- failureCallback
- ));
- } else if (fileType.match(/\.obj$/i)) {
- await new Promise(resolve => this.loadStrings(
- path,
- async lines => {
- try {
- const parsedMaterials = await getMaterials(lines);
-
- parseObj(model, lines, parsedMaterials);
-
- } catch (error) {
- if (failureCallback) {
- failureCallback(error);
- } else {
- p5._friendlyError('Error during parsing: ' + error.message);
- }
- return;
- }
- finally {
- if (normalize) {
- model.normalize();
- }
- if (flipU) {
- model.flipU();
- }
- if (flipV) {
- model.flipV();
- }
+ if (successCallback) {
+ return successCallback(model);
+ } else {
+ return model;
+ }
- resolve();
- if (typeof successCallback === 'function') {
- successCallback(model);
- }
- }
- },
- failureCallback
- ));
- } else {
+ } else if (fileType.match(/\.obj$/i)) {
+ const { data } = await request(path, 'text');
+ const lines = data.split('\n');
+
+ const parsedMaterials = await getMaterials(lines);
+ parseObj(model, lines, parsedMaterials);
+
+ if (normalize) {
+ model.normalize();
+ }
+ if (flipU) {
+ model.flipU();
+ }
+ if (flipV) {
+ model.flipV();
+ }
+
+ if (successCallback) {
+ return successCallback(model);
+ } else {
+ return model;
+ }
+ }
+ } catch(err) {
p5._friendlyFileLoadError(3, path);
- if (failureCallback) {
- failureCallback();
+ if(failureCallback) {
+ return failureCallback(err);
} else {
- p5._friendlyError(
- 'Sorry, the file type is invalid. Only OBJ and STL files are supported.'
- );
+ throw err;
}
}
- return model;
};
- function parseMtl(p5, mtlPath) {
- return new Promise((resolve, reject) => {
- let currentMaterial = null;
- let materials = {};
- p5.loadStrings(
- mtlPath,
- lines => {
- for (let line = 0; line < lines.length; ++line) {
- const tokens = lines[line].trim().split(/\s+/);
- if (tokens[0] === 'newmtl') {
- const materialName = tokens[1];
- currentMaterial = materialName;
- materials[currentMaterial] = {};
- } else if (tokens[0] === 'Kd') {
- //Diffuse color
- materials[currentMaterial].diffuseColor = [
- parseFloat(tokens[1]),
- parseFloat(tokens[2]),
- parseFloat(tokens[3])
- ];
- } else if (tokens[0] === 'Ka') {
- //Ambient Color
- materials[currentMaterial].ambientColor = [
- parseFloat(tokens[1]),
- parseFloat(tokens[2]),
- parseFloat(tokens[3])
- ];
- } else if (tokens[0] === 'Ks') {
- //Specular color
- materials[currentMaterial].specularColor = [
- parseFloat(tokens[1]),
- parseFloat(tokens[2]),
- parseFloat(tokens[3])
- ];
-
- } else if (tokens[0] === 'map_Kd') {
- //Texture path
- materials[currentMaterial].texturePath = tokens[1];
- }
- }
- resolve(materials);
- }, reject
- );
- });
+ async function parseMtl(mtlPath) {
+ let currentMaterial = null;
+ let materials = {};
+
+ const { data } = await request(mtlPath, "text");
+ const lines = data.split('\n');
+
+ for (let line = 0; line < lines.length; ++line) {
+ const tokens = lines[line].trim().split(/\s+/);
+ if (tokens[0] === 'newmtl') {
+ const materialName = tokens[1];
+ currentMaterial = materialName;
+ materials[currentMaterial] = {};
+ } else if (tokens[0] === 'Kd') {
+ //Diffuse color
+ materials[currentMaterial].diffuseColor = [
+ parseFloat(tokens[1]),
+ parseFloat(tokens[2]),
+ parseFloat(tokens[3])
+ ];
+ } else if (tokens[0] === 'Ka') {
+ //Ambient Color
+ materials[currentMaterial].ambientColor = [
+ parseFloat(tokens[1]),
+ parseFloat(tokens[2]),
+ parseFloat(tokens[3])
+ ];
+ } else if (tokens[0] === 'Ks') {
+ //Specular color
+ materials[currentMaterial].specularColor = [
+ parseFloat(tokens[1]),
+ parseFloat(tokens[2]),
+ parseFloat(tokens[3])
+ ];
+
+ } else if (tokens[0] === 'map_Kd') {
+ //Texture path
+ materials[currentMaterial].texturePath = tokens[1];
+ }
+ }
+
+ return materials;
}
/**
@@ -589,7 +572,7 @@ function loading(p5, fn){
} else if (tokens[0] === 'v' || tokens[0] === 'vn') {
// Check if this line describes a vertex or vertex normal.
// It will have three numeric parameters.
- const vertex = new p5.Vector(
+ const vertex = new Vector(
parseFloat(tokens[1]),
parseFloat(tokens[2]),
parseFloat(tokens[3])
@@ -632,7 +615,7 @@ function loading(p5, fn){
model.uvs.push(loadedVerts.vt[vertParts[1]] ?
loadedVerts.vt[vertParts[1]].slice() : [0, 0]);
model.vertexNormals.push(loadedVerts.vn[vertParts[2]] ?
- loadedVerts.vn[vertParts[2]].copy() : new p5.Vector());
+ loadedVerts.vn[vertParts[2]].copy() : new Vector());
usedVerts[vertString][currentMaterial] = vertIndex;
face.push(vertIndex);
@@ -684,6 +667,7 @@ function loading(p5, fn){
// If both are true or both are false, throw an error because the model is inconsistent
throw new Error('Model coloring is inconsistent. Either all vertices should have colors or none should.');
}
+
return model;
}
diff --git a/src/webgl/material.js b/src/webgl/material.js
index 0c0a8e035f..288d83f822 100644
--- a/src/webgl/material.js
+++ b/src/webgl/material.js
@@ -8,6 +8,7 @@
import * as constants from '../core/constants';
import { RendererGL } from './p5.RendererGL';
import { Shader } from './p5.Shader';
+import { request } from '../io/files';
function material(p5, fn){
/**
@@ -34,26 +35,27 @@ function material(p5, fn){
* The third parameter, `successCallback`, is optional. If a function is
* passed, it will be called once the shader has loaded. The callback function
* can use the new p5.Shader object as its
- * parameter.
+ * parameter. The return value of the `successCallback()` function will be used
+ * as the final return value of `loadShader()`.
*
* The fourth parameter, `failureCallback`, is also optional. If a function is
* passed, it will be called if the shader fails to load. The callback
- * function can use the event error as its parameter.
+ * function can use the event error as its parameter. The return value of the `
+ * failureCallback()` function will be used as the final return value of `loadShader()`.
*
- * Shaders can take time to load. Calling `loadShader()` in
- * preload() ensures shaders load before they're
- * used in setup() or draw() .
+ * This function returns a `Promise` and should be used in an `async` setup with
+ * `await`. See the examples for the usage syntax.
*
* Note: Shaders can only be used in WebGL mode.
*
* @method loadShader
- * @param {String} vertFilename path of the vertex shader to be loaded.
- * @param {String} fragFilename path of the fragment shader to be loaded.
+ * @param {String|Request} vertFilename path of the vertex shader to be loaded.
+ * @param {String|Request} fragFilename path of the fragment shader to be loaded.
* @param {Function} [successCallback] function to call once the shader is loaded. Can be passed the
* p5.Shader object.
* @param {Function} [failureCallback] function to call if the shader fails to load. Can be passed an
* `Error` event object.
- * @return {p5.Shader} new shader created from the vertex and fragment shader files.
+ * @return {Promise} new shader created from the vertex and fragment shader files.
*
* @example
*
@@ -63,11 +65,9 @@ function material(p5, fn){
* let mandelbrot;
*
* // Load the shader and create a p5.Shader object.
- * function preload() {
- * mandelbrot = loadShader('assets/shader.vert', 'assets/shader.frag');
- * }
+ * async function setup() {
+ * mandelbrot = await loadShader('assets/shader.vert', 'assets/shader.frag');
*
- * function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Compile and apply the p5.Shader object.
@@ -94,11 +94,9 @@ function material(p5, fn){
* let mandelbrot;
*
* // Load the shader and create a p5.Shader object.
- * function preload() {
- * mandelbrot = loadShader('assets/shader.vert', 'assets/shader.frag');
- * }
+ * async function setup() {
+ * mandelbrot = await loadShader('assets/shader.vert', 'assets/shader.frag');
*
- * function setup() {
* createCanvas(100, 100, WEBGL);
*
* // Use the p5.Shader object.
@@ -120,55 +118,32 @@ function material(p5, fn){
*
*
*/
- fn.loadShader = function (
+ fn.loadShader = async function (
vertFilename,
fragFilename,
successCallback,
failureCallback
) {
p5._validateParameters('loadShader', arguments);
- if (!failureCallback) {
- failureCallback = console.error;
- }
const loadedShader = new Shader();
- const self = this;
- let loadedFrag = false;
- let loadedVert = false;
+ try {
+ loadedShader._vertSrc = await request(vertFilename, 'text');
+ loadedShader._fragSrc = await request(fragFilename, 'text');
- const onLoad = () => {
- self._decrementPreload();
if (successCallback) {
- successCallback(loadedShader);
+ return successCallback(loadedShader);
+ } else {
+ return loadedShader
}
- };
-
- this.loadStrings(
- vertFilename,
- result => {
- loadedShader._vertSrc = result.join('\n');
- loadedVert = true;
- if (loadedFrag) {
- onLoad();
- }
- },
- failureCallback
- );
-
- this.loadStrings(
- fragFilename,
- result => {
- loadedShader._fragSrc = result.join('\n');
- loadedFrag = true;
- if (loadedVert) {
- onLoad();
- }
- },
- failureCallback
- );
-
- return loadedShader;
+ } catch(err) {
+ if (failureCallback) {
+ return failureCallback(err);
+ } else {
+ throw err;
+ }
+ }
};
/**
diff --git a/src/webgl/p5.Texture.js b/src/webgl/p5.Texture.js
index f912f552bd..7be2831076 100644
--- a/src/webgl/p5.Texture.js
+++ b/src/webgl/p5.Texture.js
@@ -6,7 +6,6 @@
* @requires core
*/
-// import p5 from '../core/main';
import * as constants from '../core/constants';
import { Element } from '../dom/p5.Element';
import { Renderer } from '../core/p5.Renderer';
diff --git a/test/js/mocks.js b/test/js/mocks.js
new file mode 100644
index 0000000000..c70284553c
--- /dev/null
+++ b/test/js/mocks.js
@@ -0,0 +1,34 @@
+import { vi } from 'vitest';
+import { http, HttpResponse, passthrough } from 'msw';
+import { setupWorker } from 'msw/browser';
+
+// HTTP requests mocks
+const httpMocks = [
+ http.get('404file', () => {
+ return new HttpResponse('Not Found', {
+ status: 404,
+ statusText: 'Not Found',
+ });
+ }),
+ http.all('*', ({request}) => {
+ return passthrough();
+ })
+];
+
+export const httpMock = setupWorker(...httpMocks);
+
+// p5.js module mocks
+export const mockP5 = {
+ _validateParameters: vi.fn(),
+ _friendlyFileLoadError: vi.fn(),
+ _friendlyError: vi.fn()
+};
+
+const mockCanvas = document.createElement('canvas');
+export const mockP5Prototype = {
+ saveCanvas: vi.fn(),
+ elt: mockCanvas,
+ _curElement: {
+ elt: mockCanvas
+ }
+};
diff --git a/test/js/p5_helpers.js b/test/js/p5_helpers.js
index a8a72daf24..363c0d462e 100644
--- a/test/js/p5_helpers.js
+++ b/test/js/p5_helpers.js
@@ -25,66 +25,6 @@ export function testSketchWithPromise(name, sketch_fn) {
return test(name, test_fn);
}
-export function testWithDownload(name, fn, asyncFn = false) {
- const test_fn = function() {
- return new Promise((resolve, reject) => {
- let blobContainer = {};
-
- const prevClick = HTMLAnchorElement.prototype.click;
- const prevDispatchEvent = HTMLAnchorElement.prototype.dispatchEvent;
- const blockDownloads = () => {
- HTMLAnchorElement.prototype.click = () => {};
- HTMLAnchorElement.prototype.dispatchEvent = () => {};
- }
- const unblockDownloads = () => {
- HTMLAnchorElement.prototype.click = prevClick;
- HTMLAnchorElement.prototype.dispatchEvent = prevDispatchEvent;
- }
-
- // create a backup of createObjectURL
- let couBackup = window.URL.createObjectURL;
-
- // file-saver uses createObjectURL as an intermediate step. If we
- // modify the definition a just a little bit we can capture whenever
- // it is called and also peek in the data that was passed to it
- window.URL.createObjectURL = blob => {
- blobContainer.blob = blob;
- return couBackup(blob);
- };
- blockDownloads();
-
- let error;
- if (asyncFn) {
- fn(blobContainer)
- .then(() => {
- window.URL.createObjectURL = couBackup;
- })
- .catch(err => {
- error = err;
- })
- .finally(() => {
- // restore createObjectURL to the original one
- window.URL.createObjectURL = couBackup;
- error ? reject(error) : resolve();
- unblockDownloads();
- });
- } else {
- try {
- fn(blobContainer);
- } catch (err) {
- error = err;
- }
- // restore createObjectURL to the original one
- window.URL.createObjectURL = couBackup;
- error ? reject(error) : resolve();
- unblockDownloads();
- }
- });
- };
-
- return test(name, test_fn);
-}
-
// Tests should run only for the unminified script
export function testUnMinified(name, test_fn) {
return !window.IS_TESTING_MINIFIED_VERSION ? test(name, test_fn) : null;
diff --git a/test/mockServiceWorker.js b/test/mockServiceWorker.js
new file mode 100644
index 0000000000..89bce29129
--- /dev/null
+++ b/test/mockServiceWorker.js
@@ -0,0 +1,295 @@
+/* eslint-disable */
+/* tslint:disable */
+
+/**
+ * Mock Service Worker.
+ * @see https://github.com/mswjs/msw
+ * - Please do NOT modify this file.
+ * - Please do NOT serve this file on production.
+ */
+
+const PACKAGE_VERSION = '2.6.5'
+const INTEGRITY_CHECKSUM = 'ca7800994cc8bfb5eb961e037c877074'
+const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
+const activeClientIds = new Set()
+
+self.addEventListener('install', function () {
+ self.skipWaiting()
+})
+
+self.addEventListener('activate', function (event) {
+ event.waitUntil(self.clients.claim())
+})
+
+self.addEventListener('message', async function (event) {
+ const clientId = event.source.id
+
+ if (!clientId || !self.clients) {
+ return
+ }
+
+ const client = await self.clients.get(clientId)
+
+ if (!client) {
+ return
+ }
+
+ const allClients = await self.clients.matchAll({
+ type: 'window',
+ })
+
+ switch (event.data) {
+ case 'KEEPALIVE_REQUEST': {
+ sendToClient(client, {
+ type: 'KEEPALIVE_RESPONSE',
+ })
+ break
+ }
+
+ case 'INTEGRITY_CHECK_REQUEST': {
+ sendToClient(client, {
+ type: 'INTEGRITY_CHECK_RESPONSE',
+ payload: {
+ packageVersion: PACKAGE_VERSION,
+ checksum: INTEGRITY_CHECKSUM,
+ },
+ })
+ break
+ }
+
+ case 'MOCK_ACTIVATE': {
+ activeClientIds.add(clientId)
+
+ sendToClient(client, {
+ type: 'MOCKING_ENABLED',
+ payload: {
+ client: {
+ id: client.id,
+ frameType: client.frameType,
+ },
+ },
+ })
+ break
+ }
+
+ case 'MOCK_DEACTIVATE': {
+ activeClientIds.delete(clientId)
+ break
+ }
+
+ case 'CLIENT_CLOSED': {
+ activeClientIds.delete(clientId)
+
+ const remainingClients = allClients.filter((client) => {
+ return client.id !== clientId
+ })
+
+ // Unregister itself when there are no more clients
+ if (remainingClients.length === 0) {
+ self.registration.unregister()
+ }
+
+ break
+ }
+ }
+})
+
+self.addEventListener('fetch', function (event) {
+ const { request } = event
+
+ // Bypass navigation requests.
+ if (request.mode === 'navigate') {
+ return
+ }
+
+ // Opening the DevTools triggers the "only-if-cached" request
+ // that cannot be handled by the worker. Bypass such requests.
+ if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
+ return
+ }
+
+ // Bypass all requests when there are no active clients.
+ // Prevents the self-unregistered worked from handling requests
+ // after it's been deleted (still remains active until the next reload).
+ if (activeClientIds.size === 0) {
+ return
+ }
+
+ // Generate unique request ID.
+ const requestId = crypto.randomUUID()
+ event.respondWith(handleRequest(event, requestId))
+})
+
+async function handleRequest(event, requestId) {
+ const client = await resolveMainClient(event)
+ const response = await getResponse(event, client, requestId)
+
+ // Send back the response clone for the "response:*" life-cycle events.
+ // Ensure MSW is active and ready to handle the message, otherwise
+ // this message will pend indefinitely.
+ if (client && activeClientIds.has(client.id)) {
+ ;(async function () {
+ const responseClone = response.clone()
+
+ sendToClient(
+ client,
+ {
+ type: 'RESPONSE',
+ payload: {
+ requestId,
+ isMockedResponse: IS_MOCKED_RESPONSE in response,
+ type: responseClone.type,
+ status: responseClone.status,
+ statusText: responseClone.statusText,
+ body: responseClone.body,
+ headers: Object.fromEntries(responseClone.headers.entries()),
+ },
+ },
+ [responseClone.body],
+ )
+ })()
+ }
+
+ return response
+}
+
+// Resolve the main client for the given event.
+// Client that issues a request doesn't necessarily equal the client
+// that registered the worker. It's with the latter the worker should
+// communicate with during the response resolving phase.
+async function resolveMainClient(event) {
+ const client = await self.clients.get(event.clientId)
+
+ if (activeClientIds.has(event.clientId)) {
+ return client
+ }
+
+ if (client?.frameType === 'top-level') {
+ return client
+ }
+
+ const allClients = await self.clients.matchAll({
+ type: 'window',
+ })
+
+ return allClients
+ .filter((client) => {
+ // Get only those clients that are currently visible.
+ return client.visibilityState === 'visible'
+ })
+ .find((client) => {
+ // Find the client ID that's recorded in the
+ // set of clients that have registered the worker.
+ return activeClientIds.has(client.id)
+ })
+}
+
+async function getResponse(event, client, requestId) {
+ const { request } = event
+
+ // Clone the request because it might've been already used
+ // (i.e. its body has been read and sent to the client).
+ const requestClone = request.clone()
+
+ function passthrough() {
+ // Cast the request headers to a new Headers instance
+ // so the headers can be manipulated with.
+ const headers = new Headers(requestClone.headers)
+
+ // Remove the "accept" header value that marked this request as passthrough.
+ // This prevents request alteration and also keeps it compliant with the
+ // user-defined CORS policies.
+ headers.delete('accept', 'msw/passthrough')
+
+ return fetch(requestClone, { headers })
+ }
+
+ // Bypass mocking when the client is not active.
+ if (!client) {
+ return passthrough()
+ }
+
+ // Bypass initial page load requests (i.e. static assets).
+ // The absence of the immediate/parent client in the map of the active clients
+ // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet
+ // and is not ready to handle requests.
+ if (!activeClientIds.has(client.id)) {
+ return passthrough()
+ }
+
+ // Notify the client that a request has been intercepted.
+ const requestBuffer = await request.arrayBuffer()
+ const clientMessage = await sendToClient(
+ client,
+ {
+ type: 'REQUEST',
+ payload: {
+ id: requestId,
+ url: request.url,
+ mode: request.mode,
+ method: request.method,
+ headers: Object.fromEntries(request.headers.entries()),
+ cache: request.cache,
+ credentials: request.credentials,
+ destination: request.destination,
+ integrity: request.integrity,
+ redirect: request.redirect,
+ referrer: request.referrer,
+ referrerPolicy: request.referrerPolicy,
+ body: requestBuffer,
+ keepalive: request.keepalive,
+ },
+ },
+ [requestBuffer],
+ )
+
+ switch (clientMessage.type) {
+ case 'MOCK_RESPONSE': {
+ return respondWithMock(clientMessage.data)
+ }
+
+ case 'PASSTHROUGH': {
+ return passthrough()
+ }
+ }
+
+ return passthrough()
+}
+
+function sendToClient(client, message, transferrables = []) {
+ return new Promise((resolve, reject) => {
+ const channel = new MessageChannel()
+
+ channel.port1.onmessage = (event) => {
+ if (event.data && event.data.error) {
+ return reject(event.data.error)
+ }
+
+ resolve(event.data)
+ }
+
+ client.postMessage(
+ message,
+ [channel.port2].concat(transferrables.filter(Boolean)),
+ )
+ })
+}
+
+async function respondWithMock(response) {
+ // Setting response status code to 0 is a no-op.
+ // However, when responding with a "Response.error()", the produced Response
+ // instance will have status code set to 0. Since it's not possible to create
+ // a Response instance with status code 0, handle that use-case separately.
+ if (response.status === 0) {
+ return Response.error()
+ }
+
+ const mockedResponse = new Response(response.body, response)
+
+ Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, {
+ value: true,
+ enumerable: true,
+ })
+
+ return mockedResponse
+}
diff --git a/test/unit/events/mouse.js b/test/unit/events/mouse.js
index e17e7fef77..0165d4199a 100644
--- a/test/unit/events/mouse.js
+++ b/test/unit/events/mouse.js
@@ -1,7 +1,7 @@
import p5 from '../../../src/app.js';
import { parallelSketches } from '../../js/p5_helpers';
-suite('Mouse Events', function() {
+suite.todo('Mouse Events', function() {
let myp5;
let canvas;
@@ -200,7 +200,7 @@ suite('Mouse Events', function() {
assert.isNumber(myp5.pwinMouseY);
});
- test('pwinMouseY should be previous vertical position of mouse relative to the window', function() {
+ test('pwinMouseY should be previous vertical position of mouse relative to the window', async function() {
window.dispatchEvent(mouseEvent1); // dispatch first mouse event
window.dispatchEvent(mouseEvent2); // dispatch second mouse event
assert.strictEqual(myp5.pwinMouseY, mouseEvent1.clientY);
diff --git a/test/unit/image/downloading.js b/test/unit/image/downloading.js
index 612653cdf9..d2bdd4a794 100644
--- a/test/unit/image/downloading.js
+++ b/test/unit/image/downloading.js
@@ -1,214 +1,199 @@
-import p5 from '../../../src/app.js';
-import { testWithDownload } from '../../js/p5_helpers';
-
-suite.todo('downloading animated gifs', function() {
- let myp5;
- let myGif;
-
- beforeAll(function() {
- return new Promise(resolve => {
- new p5(function(p) {
- p.setup = function() {
- myp5 = p;
- resolve();
- };
- });
- });
- });
-
- afterAll(function() {
- myp5.remove();
- });
+import { mockP5, mockP5Prototype } from '../../js/mocks';
+import * as fileSaver from 'file-saver';
+import { vi } from 'vitest';
+import image from '../../../src/image/image';
+import files from '../../../src/io/files';
+import loading from '../../../src/image/loading_displaying';
+import p5Image from '../../../src/image/p5.Image';
+
+vi.mock('file-saver');
+
+expect.extend({
+ tobeGif: (received) => {
+ if (received.type === 'image/gif') {
+ return {
+ message: 'expect blob to have type image/gif',
+ pass: true
+ }
+ } else {
+ return {
+ message: 'expect blob to have type image/gif',
+ pass: false
+ }
+ }
+ },
+ tobePng: (received) => {
+ if (received.type === 'image/png') {
+ return {
+ message: 'expect blob to have type image/png',
+ pass: true
+ }
+ } else {
+ return {
+ message: 'expect blob to have type image/png',
+ pass: false
+ }
+ }
+ },
+ tobeJpg: (received) => {
+ if (received.type === 'image/jpeg') {
+ return {
+ message: 'expect blob to have type image/jpeg',
+ pass: true
+ }
+ } else {
+ return {
+ message: 'expect blob to have type image/jpeg',
+ pass: false
+ }
+ }
+ }
+});
- let imagePath = 'unit/assets/nyan_cat.gif';
+const wait = async (time) => {
+ return new Promise(resolve => setTimeout(resolve, time));
+}
- beforeEach(function loadMyGif(done) {
- myp5.loadImage(imagePath, function(pImg) {
- myGif = pImg;
- done();
- });
+suite('Downloading', () => {
+ beforeAll(async function() {
+ image(mockP5, mockP5Prototype);
+ files(mockP5, mockP5Prototype);
+ loading(mockP5, mockP5Prototype);
+ p5Image(mockP5, mockP5Prototype);
});
- suite('p5.prototype.encodeAndDownloadGif', function() {
- test('should be a function', function() {
- assert.ok(myp5.encodeAndDownloadGif);
- assert.typeOf(myp5.encodeAndDownloadGif, 'function');
- });
- test('should not throw an error', function() {
- myp5.encodeAndDownloadGif(myGif);
- });
- testWithDownload('should download a gif', function(blobContainer) {
- myp5.encodeAndDownloadGif(myGif);
- let gifBlob = blobContainer.blob;
- assert.strictEqual(gifBlob.type, 'image/gif');
- });
+ afterEach(() => {
+ vi.clearAllMocks();
});
-});
-suite.todo('p5.prototype.saveCanvas', function() {
- let myp5;
+ suite('downloading animated gifs', function() {
+ let myGif;
+ const imagePath = '/test/unit/assets/nyan_cat.gif';
- let waitForBlob = async function(blc) {
- let sleep = function(ms) {
- return new Promise(r => setTimeout(r, ms));
- };
- while (!blc.blob) {
- await sleep(5);
- }
- };
-
- beforeAll(function() {
- return new Promise(resolve => {
- new p5(function(p) {
- p.setup = function() {
- myp5 = p;
- myCanvas = p.createCanvas(20, 20);
- p.background(255, 0, 0);
- resolve();
- };
- });
+ beforeAll(async function() {
+ myGif = await mockP5Prototype.loadImage(imagePath);
});
- });
-
- afterAll(function() {
- myp5.remove();
- });
-
- test('should be a function', function() {
- assert.ok(myp5.saveCanvas);
- assert.typeOf(myp5.saveCanvas, 'function');
- });
- testWithDownload(
- 'should download a png file',
- async function(blobContainer) {
- myp5.saveCanvas();
- // since a function with callback is used in saveCanvas
- // until the blob is made available to us.
- await waitForBlob(blobContainer);
- let myBlob = blobContainer.blob;
- assert.strictEqual(myBlob.type, 'image/png');
- },
- true
- );
-
- testWithDownload(
- 'should download a jpg file I',
- async function(blobContainer) {
- myp5.saveCanvas('filename.jpg');
- await waitForBlob(blobContainer);
- let myBlob = blobContainer.blob;
- assert.strictEqual(myBlob.type, 'image/jpeg');
- },
- true
- );
-
- testWithDownload(
- 'should download a jpg file II',
- async function(blobContainer) {
- myp5.saveCanvas('filename', 'jpg');
- await waitForBlob(blobContainer);
- let myBlob = blobContainer.blob;
- assert.strictEqual(myBlob.type, 'image/jpeg');
- },
- true
- );
-});
+ suite('p5.prototype.encodeAndDownloadGif', function() {
+ test('should be a function', function() {
+ assert.ok(mockP5Prototype.encodeAndDownloadGif);
+ assert.typeOf(mockP5Prototype.encodeAndDownloadGif, 'function');
+ });
-suite('p5.prototype.saveFrames', function() {
- let myp5;
+ test('should not throw an error', function() {
+ mockP5Prototype.encodeAndDownloadGif(myGif);
+ });
- beforeAll(function() {
- return new Promise(resolve => {
- new p5(function(p) {
- p.setup = function() {
- myp5 = p;
- p.createCanvas(10, 10);
- resolve();
- };
+ test('should download a gif', async () => {
+ mockP5Prototype.encodeAndDownloadGif(myGif);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs)
+ .toHaveBeenCalledWith(
+ expect.tobeGif(),
+ 'untitled.gif'
+ );
});
});
});
- afterAll(function() {
- myp5.remove();
- });
+ suite('p5.prototype.saveCanvas', function() {
+ test('should be a function', function() {
+ assert.ok(mockP5Prototype.saveCanvas);
+ assert.typeOf(mockP5Prototype.saveCanvas, 'function');
+ });
- test('should be a function', function() {
- assert.ok(myp5.saveFrames);
- assert.typeOf(myp5.saveFrames, 'function');
- });
+ test('should download a png file', async () => {
+ mockP5Prototype.saveCanvas();
+ await wait(100);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs)
+ .toHaveBeenCalledWith(
+ expect.tobePng(),
+ 'untitled.png'
+ );
+ });
- test('should get frames in callback (png)', function(done) {
- myp5.saveFrames('aaa', 'png', 0.5, 25, function cb1(arr) {
- assert.typeOf(arr, 'array', 'we got an array');
- for (let i = 0; i < arr.length; i++) {
- assert.ok(arr[i].imageData);
- assert.strictEqual(arr[i].ext, 'png');
- assert.strictEqual(arr[i].filename, `aaa${i}`);
- }
- done();
+ test('should download a jpg file I', async () => {
+ mockP5Prototype.saveCanvas('filename.jpg');
+ await wait(100);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs)
+ .toHaveBeenCalledWith(
+ expect.tobeJpg(),
+ 'filename.jpg'
+ );
});
- });
- test('should get frames in callback (jpg)', function(done) {
- myp5.saveFrames('bbb', 'jpg', 0.5, 25, function cb2(arr2) {
- assert.typeOf(arr2, 'array', 'we got an array');
- for (let i = 0; i < arr2.length; i++) {
- assert.ok(arr2[i].imageData);
- assert.strictEqual(arr2[i].ext, 'jpg');
- assert.strictEqual(arr2[i].filename, `bbb${i}`);
- }
- done();
+ test('should download a jpg file II', async () => {
+ mockP5Prototype.saveCanvas('filename', 'jpg');
+ await wait(100);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs)
+ .toHaveBeenCalledWith(
+ expect.tobeJpg(),
+ 'filename.jpg'
+ );
});
});
-});
-suite('p5.prototype.saveGif', function() {
- let myp5;
+ suite('p5.prototype.saveFrames', function() {
+ test('should be a function', function() {
+ assert.ok(mockP5Prototype.saveFrames);
+ assert.typeOf(mockP5Prototype.saveFrames, 'function');
+ });
- let waitForBlob = async function(blc) {
- let sleep = function(ms) {
- return new Promise(r => setTimeout(r, ms));
- };
- while (!blc.blob) {
- await sleep(5);
- }
- };
-
- beforeAll(function() {
- return new Promise(resolve => {
- new p5(function(p) {
- p.setup = function() {
- myp5 = p;
- p.createCanvas(10, 10);
+ test('should get frames in callback (png)', async () => {
+ return new Promise(resolve => {
+ mockP5Prototype.saveFrames('aaa', 'png', 0.5, 25, function cb1(arr) {
+ assert.typeOf(arr, 'array', 'we got an array');
+ for (let i = 0; i < arr.length; i++) {
+ assert.ok(arr[i].imageData);
+ assert.equal(arr[i].ext, 'png');
+ assert.equal(arr[i].filename, `aaa${i}`);
+ }
resolve();
- };
+ });
});
});
- });
- afterAll(function() {
- myp5.remove();
+ test('should get frames in callback (png)', async () => {
+ return new Promise(resolve => {
+ mockP5Prototype.saveFrames('aaa', 'jpg', 0.5, 25, function cb1(arr) {
+ assert.typeOf(arr, 'array', 'we got an array');
+ for (let i = 0; i < arr.length; i++) {
+ assert.ok(arr[i].imageData);
+ assert.equal(arr[i].ext, 'jpg');
+ assert.equal(arr[i].filename, `aaa${i}`);
+ }
+ resolve();
+ });
+ });
+ });
});
- test('should be a function', function() {
- assert.ok(myp5.saveGif);
- assert.typeOf(myp5.saveGif, 'function');
- });
+ suite('p5.prototype.saveGif', function() {
+ test('should be a function', function() {
+ assert.ok(mockP5Prototype.saveGif);
+ assert.typeOf(mockP5Prototype.saveGif, 'function');
+ });
- test('should not throw an error', function() {
- myp5.saveGif('myGif', 3);
- });
+ // TODO: this implementation need refactoring
+ test.todo('should not throw an error', async () => {
+ await mockP5Prototype.saveGif('myGif', 3);
+ });
- test('should not throw an error', function() {
- myp5.saveGif('myGif', 3, { delay: 2, frames: 'seconds' });
- });
+ test.todo('should not throw an error', async () => {
+ await mockP5Prototype.saveGif('myGif', 3, { delay: 2, frames: 'seconds' });
+ });
- testWithDownload('should download a GIF', async function(blobContainer) {
- myp5.saveGif('myGif', 3, 2);
- await waitForBlob(blobContainer);
- let gifBlob = blobContainer.blob;
- assert.strictEqual(gifBlob.type, 'image/gif');
+ test.todo('should download a GIF', async () => {
+ await mockP5Prototype.saveGif('myGif', 3, 2);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs)
+ .toHaveBeenCalledWith(
+ expect.tobeGif(),
+ 'myGif.gif'
+ );
+ });
});
});
diff --git a/test/unit/image/loading.js b/test/unit/image/loading.js
index 4b81d1193f..801705294e 100644
--- a/test/unit/image/loading.js
+++ b/test/unit/image/loading.js
@@ -1,3 +1,7 @@
+import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
+import loadingDisplaying from '../../../src/image/loading_displaying';
+import image from '../../../src/image/p5.Image';
+
import p5 from '../../../src/app.js';
import { vi } from 'vitest';
@@ -28,374 +32,212 @@ var testImageRender = function(file, sketch) {
});
};
-suite.todo('loading images', function() {
- var myp5;
-
- beforeAll(function() {
- new p5(function(p) {
- p.setup = function() {
- myp5 = p;
- };
-
- // Make sure draw() exists so timing functions still run each frame
- // and we can test gif animation
- p.draw = function() {};
- });
- });
+suite('loading images', function() {
+ const imagePath = '/test/unit/assets/cat.jpg';
+ const singleFrameGif = '/test/unit/assets/target_small.gif';
+ const animatedGif = '/test/unit/assets/white_black.gif';
+ const nyanCatGif = '/test/unit/assets/nyan_cat.gif';
+ const disposeNoneGif = '/test/unit/assets/dispose_none.gif';
+ const disposeBackgroundGif = '/test/unit/assets/dispose_background.gif';
+ const disposePreviousGif = '/test/unit/assets/dispose_previous.gif';
+ const invalidFile = '404file';
- afterAll(function() {
- myp5.remove();
+ beforeAll(async function() {
+ loadingDisplaying(mockP5, mockP5Prototype);
+ image(mockP5, mockP5Prototype);
+ await httpMock.start({quiet: true});
});
- var imagePath = 'unit/assets/cat.jpg';
-
- test('should call successCallback when image loads', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage(imagePath, resolve, reject);
- }).then(function(pImg) {
- assert.ok(pImg, 'cat.jpg loaded');
- assert.isTrue(pImg instanceof p5.Image);
- });
+ test('throws error when encountering HTTP errors', async () => {
+ await expect(mockP5Prototype.loadImage(invalidFile))
+ .rejects
+ .toThrow('Not Found');
});
- test('should call failureCallback when unable to load image', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage(
- 'invalid path',
- function(pImg) {
- reject('Entered success callback.');
- },
- resolve
- );
- }).then(function(event) {
- assert.equal(event.type, 'error');
- });
- });
-
- test('should draw image with defaults', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage('unit/assets/cat.jpg', resolve, reject);
- }).then(function(img) {
- myp5.image(img, 0, 0);
- return testImageRender('unit/assets/cat.jpg', myp5).then(function(res) {
- assert.isTrue(res);
+ test('error callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadImage(invalidFile, () => {
+ reject("Success callback executed");
+ }, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
});
});
});
- test('static image should not have gifProperties', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage('unit/assets/cat.jpg', resolve, reject);
- }).then(function(img) {
- assert.isTrue(img.gifProperties === null);
+ test('success callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadImage(imagePath, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ }, (err) => {
+ reject(`Error callback called: ${err.toString()}`);
+ });
});
});
- test('single frame GIF should not have gifProperties', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage('unit/assets/target_small.gif', resolve, reject);
- }).then(function(img) {
- assert.isTrue(img.gifProperties === null);
- });
+ test('returns an object with correct data', async () => {
+ const pImg = await mockP5Prototype.loadImage(imagePath);
+ assert.ok(pImg, 'cat.jpg loaded');
+ assert.isTrue(pImg instanceof mockP5.Image);
});
- test('first frame of GIF should be painted after load', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage('unit/assets/white_black.gif', resolve, reject);
- }).then(function(img) {
- assert.deepEqual(img.get(0, 0), [255, 255, 255, 255]);
+ test('passes an object with correct data to success callback', async () => {
+ await mockP5Prototype.loadImage(imagePath, (pImg) => {
+ assert.ok(pImg, 'cat.jpg loaded');
+ assert.isTrue(pImg instanceof mockP5.Image);
});
});
- test('animated gifs animate correctly', function() {
- const wait = function(ms) {
- return new Promise(function(resolve) {
- setTimeout(resolve, ms);
- });
- };
- let img;
- return new Promise(function(resolve, reject) {
- img = myp5.loadImage('unit/assets/nyan_cat.gif', resolve, reject);
- }).then(function() {
- assert.equal(img.gifProperties.displayIndex, 0);
- myp5.image(img, 0, 0);
-
- // This gif has frames that are around for 100ms each.
- // After 100ms has elapsed, the display index should
- // increment when we draw the image.
- return wait(100);
- }).then(function() {
- return new Promise(function(resolve) {
- window.requestAnimationFrame(resolve);
- });
- }).then(function() {
- myp5.image(img, 0, 0);
- assert.equal(img.gifProperties.displayIndex, 1);
- });
- });
+ // TODO: this is more of an integration test, possibly delegate to visual test
+ // test('should draw image with defaults', function() {
+ // return new Promise(function(resolve, reject) {
+ // myp5.loadImage('unit/assets/cat.jpg', resolve, reject);
+ // }).then(function(img) {
+ // myp5.image(img, 0, 0);
+ // return testImageRender('unit/assets/cat.jpg', myp5).then(function(res) {
+ // assert.isTrue(res);
+ // });
+ // });
+ // });
- var backgroundColor = [135, 206, 235, 255];
- var blue = [0, 0, 255, 255];
- var transparent = [0, 0, 0, 0];
- test('animated gifs work with no disposal', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage('unit/assets/dispose_none.gif', resolve, reject);
- }).then(function(img) {
- // Frame 0 shows the background
- assert.deepEqual(img.get(7, 12), backgroundColor);
- // Frame 1 draws on top of the background
- img.setFrame(1);
- assert.deepEqual(img.get(7, 12), blue);
- // Frame 2 does not erase untouched parts of frame 2
- img.setFrame(2);
- assert.deepEqual(img.get(7, 12), blue);
- });
+ test('static image should not have gifProperties', async () => {
+ const img = await mockP5Prototype.loadImage(imagePath);
+ assert.isNull(img.gifProperties);
});
- test('animated gifs work with background disposal', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage('unit/assets/dispose_background.gif', resolve, reject);
- }).then(function(img) {
- // Frame 0 shows the background
- assert.deepEqual(img.get(7, 12), backgroundColor);
- // Frame 1 draws on top of the background
- img.setFrame(1);
- assert.deepEqual(img.get(7, 12), blue);
- // Frame 2 erases the content added in frame 2
- img.setFrame(2);
- assert.deepEqual(img.get(7, 12), transparent);
- });
+ test('single frame GIF should not have gifProperties', async () => {
+ const img = await mockP5Prototype.loadImage(singleFrameGif);
+ assert.isNull(img.gifProperties);
});
- test('animated gifs work with previous disposal', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage('unit/assets/dispose_previous.gif', resolve, reject);
- }).then(function(img) {
- // Frame 0 shows the background
- assert.deepEqual(img.get(7, 12), backgroundColor);
- // Frame 1 draws on top of the background
- img.setFrame(1);
- assert.deepEqual(img.get(7, 12), blue);
- // Frame 2 returns the content added in frame 2 to its previous value
- img.setFrame(2);
- assert.deepEqual(img.get(7, 12), backgroundColor);
- });
+ test('first frame of GIF should be painted after load', async () => {
+ const img = await mockP5Prototype.loadImage(animatedGif);
+ assert.deepEqual(img.get(0, 0), [255, 255, 255, 255]);
});
- /* TODO: make this resilient to platform differences in image resizing.
- test('should draw cropped image', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage('unit/assets/target.gif', resolve, reject);
- }).then(function(img) {
- myp5.image(img, 0, 0, 6, 6, 5, 5, 6, 6);
- return testImageRender('unit/assets/target_small.gif', myp5).then(
- function(res) {
- assert.isTrue(res);
- }
- );
- });
- });
- */
-
- // Test loading image in preload() with success callback
- // test('Test in preload() with success callback');
- // test('Test in setup() after preload()');
- // These tests don't work correctly (You can't use suite and test like that)
- // they simply get added at the root level.
- // var mySketch = function(this_p5) {
- // var myImage;
- // this_p5.preload = function() {
- // suite('Test in preload() with success callback', function() {
- // test('Load asynchronously and use success callback', function(done) {
- // myImage = this_p5.loadImage('unit/assets/cat.jpg', function() {
- // assert.ok(myImage);
- // done();
- // });
- // });
+ // test('animated gifs animate correctly', function() {
+ // const wait = function(ms) {
+ // return new Promise(function(resolve) {
+ // setTimeout(resolve, ms);
// });
// };
-
- // this_p5.setup = function() {
- // suite('setup() after preload() with success callback', function() {
- // test('should be loaded if preload() finished', function(done) {
- // assert.isTrue(myImage instanceof p5.Image);
- // assert.isTrue(myImage.width > 0 && myImage.height > 0);
- // done();
- // });
+ // let img;
+ // return new Promise(function(resolve, reject) {
+ // img = myp5.loadImage('unit/assets/nyan_cat.gif', resolve, reject);
+ // }).then(function() {
+ // assert.equal(img.gifProperties.displayIndex, 0);
+ // myp5.image(img, 0, 0);
+
+ // // This gif has frames that are around for 100ms each.
+ // // After 100ms has elapsed, the display index should
+ // // increment when we draw the image.
+ // return wait(100);
+ // }).then(function() {
+ // return new Promise(function(resolve) {
+ // window.requestAnimationFrame(resolve);
// });
- // };
- // };
- // new p5(mySketch, null, false);
-
- // // Test loading image in preload() without success callback
- // mySketch = function(this_p5) {
- // var myImage;
- // this_p5.preload = function() {
- // myImage = this_p5.loadImage('unit/assets/cat.jpg');
- // };
-
- // this_p5.setup = function() {
- // suite('setup() after preload() without success callback', function() {
- // test('should be loaded now preload() finished', function(done) {
- // assert.isTrue(myImage instanceof p5.Image);
- // assert.isTrue(myImage.width > 0 && myImage.height > 0);
- // done();
- // });
- // });
- // };
- // };
- // new p5(mySketch, null, false);
-
- // // Test loading image failure in preload() without failure callback
- // mySketch = function(this_p5) {
- // this_p5.preload = function() {
- // this_p5.loadImage('', function() {
- // throw new Error('Should not be called');
- // });
- // };
-
- // this_p5.setup = function() {
- // throw new Error('Should not be called');
- // };
- // };
- // new p5(mySketch, null, false);
-
- // // Test loading image failure in preload() with failure callback
- // mySketch = function(this_p5) {
- // var myImage;
- // this_p5.preload = function() {
- // suite('Test loading image failure in preload() with failure callback', function() {
- // test('Load fail and use failure callback', function(done) {
- // myImage = this_p5.loadImage('', function() {
- // assert.fail();
- // done();
- // }, function() {
- // assert.ok(myImage);
- // done();
- // });
- // });
- // });
- // };
-
- // this_p5.setup = function() {
- // suite('setup() after preload() failure with failure callback', function() {
- // test('should be loaded now preload() finished', function(done) {
- // assert.isTrue(myImage instanceof p5.Image);
- // assert.isTrue(myImage.width === 1 && myImage.height === 1);
- // done();
- // });
- // });
- // };
- // };
- // new p5(mySketch, null, false);
-});
-
-suite.todo('loading animated gif images', function() {
- var myp5;
+ // }).then(function() {
+ // myp5.image(img, 0, 0);
+ // assert.equal(img.gifProperties.displayIndex, 1);
+ // });
+ // });
- beforeAll(function() {
- new p5(function(p) {
- p.setup = function() {
- myp5 = p;
- };
- });
+ const backgroundColor = [135, 206, 235, 255];
+ const blue = [0, 0, 255, 255];
+ const transparent = [0, 0, 0, 0];
+ test('animated gifs work with no disposal', async () => {
+ const img = await mockP5Prototype.loadImage(disposeNoneGif);
+ // Frame 0 shows the background
+ assert.deepEqual(img.get(7, 12), backgroundColor);
+ // Frame 1 draws on top of the background
+ img.setFrame(1);
+ assert.deepEqual(img.get(7, 12), blue);
+ // Frame 2 does not erase untouched parts of frame 2
+ img.setFrame(2);
+ assert.deepEqual(img.get(7, 12), blue);
});
- afterAll(function() {
- myp5.remove();
+ test('animated gifs work with background disposal', async () => {
+ const img = await mockP5Prototype.loadImage(disposeBackgroundGif);
+ // Frame 0 shows the background
+ assert.deepEqual(img.get(7, 12), backgroundColor);
+ // Frame 1 draws on top of the background
+ img.setFrame(1);
+ assert.deepEqual(img.get(7, 12), blue);
+ // Frame 2 erases the content added in frame 2
+ img.setFrame(2);
+ assert.deepEqual(img.get(7, 12), transparent);
});
- var imagePath = 'unit/assets/nyan_cat.gif';
-
- test('should call successCallback when image loads', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage(imagePath, resolve, reject);
- }).then(function(pImg) {
- assert.ok(pImg, 'nyan_cat.gif loaded');
- assert.isTrue(pImg instanceof p5.Image);
- });
+ test('animated gifs work with previous disposal', async () => {
+ const img = await mockP5Prototype.loadImage(disposePreviousGif);
+ // Frame 0 shows the background
+ assert.deepEqual(img.get(7, 12), backgroundColor);
+ // Frame 1 draws on top of the background
+ img.setFrame(1);
+ assert.deepEqual(img.get(7, 12), blue);
+ // Frame 2 returns the content added in frame 2 to its previous value
+ img.setFrame(2);
+ assert.deepEqual(img.get(7, 12), backgroundColor);
});
- test('should call failureCallback when unable to load image', function() {
- return new Promise(function(resolve, reject) {
- myp5.loadImage(
- 'invalid path',
- function(pImg) {
- reject('Entered success callback.');
- },
- resolve
+ // /* TODO: make this resilient to platform differences in image resizing.
+ // test('should draw cropped image', function() {
+ // return new Promise(function(resolve, reject) {
+ // myp5.loadImage('unit/assets/target.gif', resolve, reject);
+ // }).then(function(img) {
+ // myp5.image(img, 0, 0, 6, 6, 5, 5, 6, 6);
+ // return testImageRender('unit/assets/target_small.gif', myp5).then(
+ // function(res) {
+ // assert.isTrue(res);
+ // }
+ // );
+ // });
+ // });
+ // */
+
+ test('should construct gifProperties correctly after preload', async () => {
+ const gifImage = await mockP5Prototype.loadImage(nyanCatGif);
+ assert.isTrue(gifImage instanceof p5.Image);
+
+ const nyanCatGifProperties = {
+ displayIndex: 0,
+ loopCount: 0,
+ loopLimit: null,
+ numFrames: 6,
+ playing: true,
+ timeDisplayed: 0
+ };
+ assert.isTrue(gifImage.gifProperties !== null);
+ for (let prop in nyanCatGifProperties) {
+ assert.deepEqual(
+ gifImage.gifProperties[prop],
+ nyanCatGifProperties[prop]
);
- }).then(function(event) {
- assert.equal(event.type, 'error');
- });
- });
+ }
+ assert.deepEqual(
+ gifImage.gifProperties.numFrames,
+ gifImage.gifProperties.frames.length
+ );
+ for (let i = 0; i < gifImage.gifProperties.numFrames; i++) {
+ assert.isTrue(
+ gifImage.gifProperties.frames[i].image instanceof ImageData
+ );
+ assert.isTrue(gifImage.gifProperties.frames[i].delay === 100);
+ }
- // test('should construct gifProperties correctly after preload', function() {
- // var mySketch = function(this_p5) {
- // var gifImage;
- // this_p5.preload = function() {
- // suite('Test in preload() with success callback', function() {
- // test('Load asynchronously and use success callback', function(done) {
- // gifImage = this_p5.loadImage(imagePath, function() {
- // assert.ok(gifImage);
- // done();
- // });
- // });
- // });
- // };
-
- // this_p5.setup = function() {
- // suite('setup() after preload() with success callback', function() {
- // test('should be loaded if preload() finished', function(done) {
- // assert.isTrue(gifImage instanceof p5.Image);
- // assert.isTrue(gifImage.width > 0 && gifImage.height > 0);
- // done();
- // });
- // test('gifProperties should be correct after preload', function done() {
- // assert.isTrue(gifImage instanceof p5.Image);
- // var nyanCatGifProperties = {
- // displayIndex: 0,
- // loopCount: 0,
- // loopLimit: null,
- // numFrames: 6,
- // playing: true,
- // timeDisplayed: 0
- // };
- // assert.isTrue(gifImage.gifProperties !== null);
- // for (var prop in nyanCatGifProperties) {
- // assert.deepEqual(
- // gifImage.gifProperties[prop],
- // nyanCatGifProperties[prop]
- // );
- // }
- // assert.deepEqual(
- // gifImage.gifProperties.numFrames,
- // gifImage.gifProperties.frames.length
- // );
- // for (var i = 0; i < gifImage.gifProperties.numFrames; i++) {
- // assert.isTrue(
- // gifImage.gifProperties.frames[i].image instanceof ImageData
- // );
- // assert.isTrue(gifImage.gifProperties.frames[i].delay === 100);
- // }
- // });
- // test('should be able to modify gifProperties state', function() {
- // assert.isTrue(gifImage.gifProperties.timeDisplayed === 0);
- // gifImage.pause();
- // assert.isTrue(gifImage.gifProperties.playing === false);
- // gifImage.play();
- // assert.isTrue(gifImage.gifProperties.playing === true);
- // gifImage.setFrame(2);
- // assert.isTrue(gifImage.gifProperties.displayIndex === 2);
- // gifImage.reset();
- // assert.isTrue(gifImage.gifProperties.displayIndex === 0);
- // assert.isTrue(gifImage.gifProperties.timeDisplayed === 0);
- // });
- // });
- // };
- // };
- // new p5(mySketch, null, false);
- // });
+ assert.equal(gifImage.gifProperties.timeDisplayed, 0);
+ gifImage.pause();
+ assert.isFalse(gifImage.gifProperties.playing);
+ gifImage.play();
+ assert.isTrue(gifImage.gifProperties.playing);
+ gifImage.setFrame(2);
+ assert.equal(gifImage.gifProperties.displayIndex, 2);
+ gifImage.reset();
+ assert.equal(gifImage.gifProperties.displayIndex, 0);
+ assert.equal(gifImage.gifProperties.timeDisplayed, 0);
+ });
});
suite.todo('displaying images', function() {
diff --git a/test/unit/io/files.js b/test/unit/io/files.js
index 4d23fb8b66..bde79ddf91 100644
--- a/test/unit/io/files.js
+++ b/test/unit/io/files.js
@@ -1,288 +1,167 @@
-import p5 from '../../../src/app.js';
-import { testWithDownload } from '../../js/p5_helpers';
+import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
+import files from '../../../src/io/files';
+import { vi } from 'vitest';
+import * as fileSaver from 'file-saver';
-suite('Files', function() {
- var myp5;
+vi.mock('file-saver');
- beforeAll(function() {
- new p5(function(p) {
- p.setup = function() {
- myp5 = p;
- };
- });
+suite('Files', function() {
+ beforeAll(async function() {
+ files(mockP5, mockP5Prototype);
+ await httpMock.start({ quiet: true });
});
- afterAll(function() {
- myp5.remove();
+ afterEach(() => {
+ vi.clearAllMocks();
});
// httpDo
suite('httpDo()', function() {
- test('should be a function', function() {
- assert.ok(myp5.httpDo);
- assert.isFunction(myp5.httpDo);
- });
-
- test('should work when provided with just a path', function() {
- return new Promise(function(resolve, reject) {
- myp5.httpDo('unit/assets/sentences.txt', resolve, reject);
- }).then(function(data) {
- assert.ok(data);
- assert.isString(data);
- });
+ test('should work when provided with just a path', async function() {
+ const data = await mockP5Prototype.httpDo('/test/unit/assets/sentences.txt');
+ assert.ok(data);
+ assert.isString(data);
});
- test('should accept method parameter', function() {
- return new Promise(function(resolve, reject) {
- myp5.httpDo('unit/assets/sentences.txt', 'GET', resolve, reject);
- }).then(function(data) {
- assert.ok(data);
- assert.isString(data);
- });
+ test('should accept method parameter', async function() {
+ const data = await mockP5Prototype.httpDo('/test/unit/assets/sentences.txt', 'GET');
+ assert.ok(data);
+ assert.isString(data);
});
- test('should accept type parameter', function() {
- return new Promise(function(resolve, reject) {
- myp5.httpDo('unit/assets/array.json', 'text', resolve, reject);
- }).then(function(data) {
- assert.ok(data);
- assert.isString(data);
- });
+ test('should accept method and type parameter together', async function() {
+ const data = await mockP5Prototype.httpDo('/test/unit/assets/sentences.txt', 'GET', 'text');
+ assert.ok(data);
+ assert.isString(data);
});
- test('should accept method and type parameter together', function() {
- return new Promise(function(resolve, reject) {
- myp5.httpDo('unit/assets/array.json', 'GET', 'text', resolve, reject);
- }).then(function(data) {
- assert.ok(data);
- assert.isString(data);
- });
- });
-
- test.todo('should pass error object to error callback function', function() {
- return new Promise(function(resolve, reject) {
- myp5.httpDo(
- 'unit/assets/sen.txt',
- function(data) {
- console.log(data);
- reject('Incorrectly succeeded.');
- },
- resolve
- );
- }).then(function(err) {
- assert.isFalse(err.ok, 'err.ok is false');
- assert.equal(err.status, 404, 'Error status is 404');
- });
- });
-
- test('should return a promise', function() {
- var promise = myp5.httpDo('unit/assets/sentences.txt');
- assert.instanceOf(promise, Promise);
- return promise.then(function(data) {
- assert.ok(data);
- assert.isString(data);
- });
- });
-
- test('should return a promise that rejects on error', function() {
- return new Promise(function(resolve, reject) {
- var promise = myp5.httpDo('404file');
- assert.instanceOf(promise, Promise);
- promise.then(function(data) {
- reject(new Error('promise resolved.'));
- });
- resolve(
- promise.catch(function(error) {
- assert.instanceOf(error, Error);
- })
- );
- });
+ test('should handle promise error correctly', async function() {
+ await expect(mockP5Prototype.httpDo('/test/unit/assets/sen.txt'))
+ .rejects
+ .toThrow('Not Found');
});
});
// saveStrings()
suite('p5.prototype.saveStrings', function() {
test('should be a function', function() {
- assert.ok(myp5.saveStrings);
- assert.typeOf(myp5.saveStrings, 'function');
+ assert.ok(mockP5Prototype.saveStrings);
+ assert.typeOf(mockP5Prototype.saveStrings, 'function');
});
- testWithDownload(
- 'should download a file with expected contents',
- async function(blobContainer) {
- let strings = ['some', 'words'];
+ test('should download a file with expected contents', async () => {
+ const strings = ['some', 'words'];
+ mockP5Prototype.saveStrings(strings, 'myfile');
- myp5.saveStrings(strings, 'myfile');
-
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- // Each element on a separate line with a trailing line-break
- assert.strictEqual(text, strings.join('\n') + '\n');
- },
- true
- );
+ const saveData = new Blob([strings.join('\n')]);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile.txt');
+ });
- testWithDownload(
- 'should download a file with expected contents with CRLF',
- async function(blobContainer) {
- let strings = ['some', 'words'];
+ test('should download a file with expected contents with CRLF', async () => {
+ const strings = ['some', 'words'];
+ mockP5Prototype.saveStrings(strings, 'myfile', 'txt', true);
- myp5.saveStrings(strings, 'myfile', 'txt', true);
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- // Each element on a separate line with a trailing CRLF
- assert.strictEqual(text, strings.join('\r\n') + '\r\n');
- },
- true
- );
+ const saveData = new Blob([strings.join('\r\n')]);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile.txt');
+ });
});
// saveJSON()
suite('p5.prototype.saveJSON', function() {
test('should be a function', function() {
- assert.ok(myp5.saveJSON);
- assert.typeOf(myp5.saveJSON, 'function');
+ assert.ok(mockP5Prototype.saveJSON);
+ assert.typeOf(mockP5Prototype.saveJSON, 'function');
});
- testWithDownload(
- 'should download a file with expected contents',
- async function(blobContainer) {
- let myObj = { hi: 'hello' };
+ test('should download a file with expected contents', async () => {
+ const myObj = { hi: 'hello' };
+ mockP5Prototype.saveJSON(myObj, 'myfile');
- myp5.saveJSON(myObj, 'myfile');
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- let json = JSON.parse(text);
- // Each element on a separate line with a trailing line-break
- assert.deepEqual(myObj, json);
- },
- true // asyncFn = true
- );
+ const saveData = new Blob([JSON.stringify(myObj, null, 2)]);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile.json');
+ });
});
// writeFile()
suite('p5.prototype.writeFile', function() {
test('should be a function', function() {
- assert.ok(myp5.writeFile);
- assert.typeOf(myp5.writeFile, 'function');
+ assert.ok(mockP5Prototype.writeFile);
+ assert.typeOf(mockP5Prototype.writeFile, 'function');
});
- testWithDownload(
- 'should download a file with expected contents (text)',
- async function(blobContainer) {
- let myArray = ['hello', 'hi'];
- myp5.writeFile(myArray, 'myfile');
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- assert.strictEqual(text, myArray.join(''));
- },
- true // asyncFn = true
- );
+ test('should download a file with expected contents (text)', async () => {
+ const myArray = ['hello', 'hi'];
+ mockP5Prototype.writeFile(myArray, 'myfile');
+
+ const saveData = new Blob(myArray);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile');
+ });
});
// downloadFile()
suite('p5.prototype.downloadFile', function() {
test('should be a function', function() {
- assert.ok(myp5.writeFile);
- assert.typeOf(myp5.writeFile, 'function');
+ assert.ok(mockP5Prototype.downloadFile);
+ assert.typeOf(mockP5Prototype.downloadFile, 'function');
+ });
+
+ test('should download a file with expected contents', async () => {
+ const myArray = ['hello', 'hi'];
+ const inBlob = new Blob(myArray);
+ mockP5Prototype.downloadFile(inBlob, 'myfile');
+
+ const saveData = new Blob(myArray);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'myfile');
});
- testWithDownload(
- 'should download a file with expected contents',
- async function(blobContainer) {
- let myArray = ['hello', 'hi'];
- let inBlob = new Blob(myArray);
- myp5.downloadFile(inBlob, 'myfile');
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- assert.strictEqual(text, myArray.join(''));
- },
- true // asyncFn = true
- );
});
// save()
suite('p5.prototype.save', function() {
suite('saving images', function() {
- let waitForBlob = async function(blc) {
- let sleep = function(ms) {
- return new Promise(r => setTimeout(r, ms));
- };
- while (!blc.blob) {
- await sleep(5);
- }
- };
- beforeAll(function() {
- myp5.createCanvas(20, 20);
- myp5.background(255, 0, 0);
- });
-
test('should be a function', function() {
- assert.ok(myp5.save);
- assert.typeOf(myp5.save, 'function');
+ assert.ok(mockP5Prototype.save);
+ assert.typeOf(mockP5Prototype.save, 'function');
});
- testWithDownload(
- 'should download a png file',
- async function(blobContainer) {
- myp5.save();
- await waitForBlob(blobContainer);
- let myBlob = blobContainer.blob;
- assert.strictEqual(myBlob.type, 'image/png');
-
- blobContainer.blob = null;
- let gb = myp5.createGraphics(100, 100);
- myp5.save(gb);
- await waitForBlob(blobContainer);
- myBlob = blobContainer.blob;
- assert.strictEqual(myBlob.type, 'image/png');
- },
- true
- );
-
- testWithDownload(
- 'should download a jpg file',
- async function(blobContainer) {
- myp5.save('filename.jpg');
- await waitForBlob(blobContainer);
- let myBlob = blobContainer.blob;
- assert.strictEqual(myBlob.type, 'image/jpeg');
+ // Test the call to `saveCanvas`
+ // `saveCanvas` is responsible for testing download is correct
+ test('should call saveCanvas', async () => {
+ mockP5Prototype.save();
+ expect(mockP5Prototype.saveCanvas).toHaveBeenCalledTimes(1);
+ expect(mockP5Prototype.saveCanvas).toHaveBeenCalledWith(mockP5Prototype.elt);
+ });
- blobContainer.blob = null;
- let gb = myp5.createGraphics(100, 100);
- myp5.save(gb, 'filename.jpg');
- await waitForBlob(blobContainer);
- myBlob = blobContainer.blob;
- assert.strictEqual(myBlob.type, 'image/jpeg');
- },
- true
- );
+ test('should call saveCanvas with filename', async () => {
+ mockP5Prototype.save('filename.jpg');
+ expect(mockP5Prototype.saveCanvas).toHaveBeenCalledTimes(1);
+ expect(mockP5Prototype.saveCanvas)
+ .toHaveBeenCalledWith(mockP5Prototype.elt, 'filename.jpg');
+ });
});
suite('saving strings and json', function() {
- testWithDownload(
- 'should download a text file',
- async function(blobContainer) {
- let myStrings = ['aaa', 'bbb'];
- myp5.save(myStrings, 'filename');
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- assert.strictEqual(text, myStrings.join('\n') + '\n');
- },
- true
- );
+ test('should download a text file', async () => {
+ const myStrings = ['aaa', 'bbb'];
+ mockP5Prototype.save(myStrings, 'filename');
- testWithDownload(
- 'should download a json file',
- async function(blobContainer) {
- let myObj = { hi: 'hello' };
- myp5.save(myObj, 'filename.json');
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- let outObj = JSON.parse(text);
- assert.deepEqual(outObj, myObj);
- },
- true
- );
+ const saveData = new Blob([myStrings.join('\n')]);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'filename.txt');
+ });
+
+ test('should download a json file', async () => {
+ const myObj = { hi: 'hello' };
+ mockP5Prototype.save(myObj, 'filename.json');
+
+ const saveData = new Blob([JSON.stringify(myObj, null, 2)]);
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs).toHaveBeenCalledWith(saveData, 'filename.json');
+ });
});
});
});
diff --git a/test/unit/io/loadBytes.js b/test/unit/io/loadBytes.js
index 296a996afc..34efe595a2 100644
--- a/test/unit/io/loadBytes.js
+++ b/test/unit/io/loadBytes.js
@@ -1,134 +1,71 @@
-import { testSketchWithPromise, promisedSketch } from '../../js/p5_helpers';
+import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
+import files from '../../../src/io/files';
-suite.todo('loadBytes', function() {
- var invalidFile = '404file';
- var validFile = 'unit/assets/nyan_cat.gif';
+suite('loadBytes', function() {
+ const invalidFile = '404file';
+ const validFile = '/test/unit/assets/nyan_cat.gif';
- testSketchWithPromise('error prevents sketch continuing', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadBytes(invalidFile);
- setTimeout(resolve, 50);
- };
-
- sketch.setup = function() {
- reject(new Error('Setup called'));
- };
-
- sketch.draw = function() {
- reject(new Error('Draw called'));
- };
+ beforeAll(async () => {
+ files(mockP5, mockP5Prototype);
+ await httpMock.start({ quiet: true });
});
- testSketchWithPromise('error callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadBytes(
- invalidFile,
- function() {
- reject(new Error('Success callback executed.'));
- },
- function() {
- // Wait a bit so that if both callbacks are executed we will get an error.
- setTimeout(resolve, 50);
- }
- );
- };
+ test('throws error when encountering HTTP errors', async () => {
+ await expect(mockP5Prototype.loadBytes(invalidFile))
+ .rejects
+ .toThrow('Not Found');
});
- testSketchWithPromise('loading correctly triggers setup', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadBytes(validFile);
- };
-
- sketch.setup = function() {
- resolve();
- };
+ test('error callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadBytes(invalidFile, () => {
+ reject("Success callback executed");
+ }, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ });
+ });
});
- testSketchWithPromise('success callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- var hasBeenCalled = false;
- sketch.preload = function() {
- sketch.loadBytes(
- validFile,
- function() {
- hasBeenCalled = true;
- },
- function(err) {
- reject(new Error('Error callback was entered: ' + err));
- }
- );
- };
-
- sketch.setup = function() {
- if (!hasBeenCalled) {
- reject(new Error('Setup called prior to success callback'));
- } else {
+ test('success callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadBytes(validFile, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
setTimeout(resolve, 50);
- }
- };
+ }, (err) => {
+ reject(`Error callback called: ${err.toString()}`);
+ });
+ });
});
- test('returns the correct object', async function() {
- const object = await promisedSketch(function(sketch, resolve, reject) {
- var _object;
- sketch.preload = function() {
- _object = sketch.loadBytes(validFile, function() {}, reject);
- };
+ test('returns the correct object', async () => {
+ const data = await mockP5Prototype.loadBytes(validFile);
+ assert.instanceOf(data, Uint8Array);
- sketch.setup = function() {
- resolve(_object);
- };
- });
- assert.isObject(object);
- // Check data format
- expect(object.bytes).to.satisfy(function(v) {
- return Array.isArray(v) || v instanceof Uint8Array;
- });
// Validate data
- var str = 'GIF89a';
+ const str = 'GIF89a';
// convert the string to a byte array
- var rgb = str.split('').map(function(e) {
+ const rgb = str.split('').map(function(e) {
return e.charCodeAt(0);
});
// this will convert a Uint8Aray to [], if necessary:
- var loaded = Array.prototype.slice.call(object.bytes, 0, str.length);
+ const loaded = Array.prototype.slice.call(data, 0, str.length);
assert.deepEqual(loaded, rgb);
});
- test('passes an object to success callback for object JSON', async function() {
- const object = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadBytes(validFile, resolve, reject);
- };
- });
- assert.isObject(object);
- // Check data format
- expect(object.bytes).to.satisfy(function(v) {
- return Array.isArray(v) || v instanceof Uint8Array;
- });
- // Validate data
- var str = 'GIF89a';
- // convert the string to a byte array
- var rgb = str.split('').map(function(e) {
- return e.charCodeAt(0);
+ test('passes athe correct object to success callback', async () => {
+ await mockP5Prototype.loadBytes(validFile, (data) => {
+ assert.instanceOf(data, Uint8Array);
+
+ // Validate data
+ const str = 'GIF89a';
+ // convert the string to a byte array
+ const rgb = str.split('').map(function(e) {
+ return e.charCodeAt(0);
+ });
+ // this will convert a Uint8Aray to [], if necessary:
+ const loaded = Array.prototype.slice.call(data, 0, str.length);
+ assert.deepEqual(loaded, rgb);
});
- // this will convert a Uint8Aray to [], if necessary:
- var loaded = Array.prototype.slice.call(object.bytes, 0, str.length);
- assert.deepEqual(loaded, rgb);
});
});
diff --git a/test/unit/io/loadImage.js b/test/unit/io/loadImage.js
deleted file mode 100644
index fd1f15317a..0000000000
--- a/test/unit/io/loadImage.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import p5 from '../../../src/app.js';
-import { testSketchWithPromise, promisedSketch } from '../../js/p5_helpers';
-
-suite.todo('loadImage', function() {
- var invalidFile = '404file';
- var validFile = 'unit/assets/nyan_cat.gif';
-
- testSketchWithPromise('error prevents sketch continuing', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadImage(invalidFile);
- setTimeout(resolve(), 50);
- };
-
- sketch.setup = function() {
- reject(new Error('Setup called'));
- };
-
- sketch.draw = function() {
- reject(new Error('Draw called'));
- };
- });
-
- testSketchWithPromise('error callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadImage(
- invalidFile,
- function() {
- reject(new Error('Success callback executed.'));
- },
- function() {
- // Wait a bit so that if both callbacks are executed we will get an error.
- setTimeout(resolve, 50);
- }
- );
- };
- });
-
- testSketchWithPromise('loading correctly triggers setup', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadImage(validFile);
- };
-
- sketch.setup = function() {
- resolve();
- };
- });
-
- testSketchWithPromise('success callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- var hasBeenCalled = false;
- sketch.preload = function() {
- sketch.loadImage(
- validFile,
- function() {
- hasBeenCalled = true;
- },
- function(err) {
- reject(new Error('Error callback was entered: ' + err));
- }
- );
- };
-
- sketch.setup = function() {
- if (!hasBeenCalled) {
- reject(new Error('Setup called prior to success callback'));
- } else {
- setTimeout(resolve, 50);
- }
- };
- });
-
- test('returns an object with correct data', async function() {
- const image = await promisedSketch(function(sketch, resolve, reject) {
- var _image;
- sketch.preload = function() {
- _image = sketch.loadImage(validFile, function() {}, reject);
- };
-
- sketch.setup = function() {
- resolve(_image);
- };
- });
- assert.instanceOf(image, p5.Image);
- });
-
- test('passes an object with correct data to callback', async function() {
- const image = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadImage(validFile, resolve, reject);
- };
- });
- assert.instanceOf(image, p5.Image);
- });
-});
diff --git a/test/unit/io/loadJSON.js b/test/unit/io/loadJSON.js
index 8882efbeb3..333695b042 100644
--- a/test/unit/io/loadJSON.js
+++ b/test/unit/io/loadJSON.js
@@ -1,137 +1,66 @@
-import { testSketchWithPromise, promisedSketch } from '../../js/p5_helpers';
+import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
+import files from '../../../src/io/files';
-suite.todo('loadJSON', function() {
- var invalidFile = '404file';
- var jsonArrayFile = 'unit/assets/array.json';
- var jsonObjectFile = 'unit/assets/object.json';
+suite('loadJSON', function() {
+ const invalidFile = '404file';
+ const jsonArrayFile = '/test/unit/assets/array.json';
+ const jsonObjectFile = '/test/unit/assets/object.json';
- testSketchWithPromise('error prevents sketch continuing', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadJSON(invalidFile, reject, function() {
- setTimeout(resolve, 50);
- });
- };
-
- sketch.setup = function() {
- reject(new Error('Entered setup'));
- };
-
- sketch.draw = function() {
- reject(new Error('Entered draw'));
- };
+ beforeAll(async () => {
+ files(mockP5, mockP5Prototype);
+ await httpMock.start({ quiet: true });
});
- testSketchWithPromise('error callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadJSON(
- invalidFile,
- function() {
- reject(new Error('Success callback executed.'));
- },
- function() {
- // Wait a bit so that if both callbacks are executed we will get an error.
- setTimeout(resolve, 50);
- }
- );
- };
+ test('throws error when encountering HTTP errors', async () => {
+ await expect(mockP5Prototype.loadJSON(invalidFile))
+ .rejects
+ .toThrow('Not Found');
});
- testSketchWithPromise('loading correctly triggers setup', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadJSON(jsonObjectFile);
- };
-
- sketch.setup = function() {
- resolve();
- };
+ test('error callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadJSON(invalidFile, () => {
+ reject("Success callback executed");
+ }, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ });
+ });
});
- testSketchWithPromise('success callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- var hasBeenCalled = false;
- sketch.preload = function() {
- sketch.loadJSON(
- jsonObjectFile,
- function() {
- hasBeenCalled = true;
- },
- function(err) {
- reject(new Error('Error callback was entered: ' + err));
- }
- );
- };
-
- sketch.setup = function() {
- if (!hasBeenCalled) {
- reject(new Error('Setup called prior to success callback'));
- } else {
+ test('success callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadJSON(jsonObjectFile, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
setTimeout(resolve, 50);
- }
- };
+ }, (err) => {
+ reject(`Error callback called: ${err.toString()}`);
+ });
+ });
});
- test('returns an object for object JSON.', async function() {
- const json = await promisedSketch(function(sketch, resolve, reject) {
- var json;
- sketch.preload = function() {
- json = sketch.loadJSON(jsonObjectFile, function() {}, reject);
- };
-
- sketch.setup = function() {
- resolve(json);
- };
- });
- assert.isObject(json);
+ test('returns an object for object JSON.', async () => {
+ const data = await mockP5Prototype.loadJSON(jsonObjectFile);
+ assert.isObject(data);
+ assert.isNotArray(data);
});
- test('passes an object to success callback for object JSON.', async function() {
- const json = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadJSON(jsonObjectFile, resolve, reject);
- };
+ test('passes an object to success callback for object JSON.', async () => {
+ await mockP5Prototype.loadJSON(jsonObjectFile, (data) => {
+ assert.isObject(data);
});
- assert.isObject(json);
});
- // Does not work with the current loadJSON.
- test('returns an array for array JSON.');
- // test('returns an array for array JSON.', async function() {
- // const json = await promisedSketch(function(sketch, resolve, reject) {
- // var json;
- // sketch.preload = function() {
- // json = sketch.loadJSON(jsonArrayFile, function() {}, reject);
- // };
- //
- // sketch.setup = function() {
- // resolve(json);
- // };
- // });
- // assert.isArray(json);
- // assert.lengthOf(json, 3);
- // });
+ test('returns an array for array JSON.', async () => {
+ const data = await mockP5Prototype.loadJSON(jsonArrayFile);
+ assert.isArray(data);
+ assert.lengthOf(data, 3);
+ });
test('passes an array to success callback for array JSON.', async function() {
- const json = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadJSON(jsonArrayFile, resolve, reject);
- };
+ await mockP5Prototype.loadJSON(jsonArrayFile, (data) => {
+ assert.isArray(data);
+ assert.lengthOf(data, 3);
});
- assert.isArray(json);
- assert.lengthOf(json, 3);
});
});
diff --git a/test/unit/io/loadModel.js b/test/unit/io/loadModel.js
index 1d94c38f54..694f87e37f 100644
--- a/test/unit/io/loadModel.js
+++ b/test/unit/io/loadModel.js
@@ -1,200 +1,118 @@
-import { testSketchWithPromise, promisedSketch } from '../../js/p5_helpers';
-
-suite.todo('loadModel', function() {
- var invalidFile = '404file';
- var validFile = 'unit/assets/teapot.obj';
- var validObjFileforMtl='unit/assets/octa-color.obj';
- var validSTLfile = 'unit/assets/ascii.stl';
- var inconsistentColorObjFile = 'unit/assets/eg1.obj';
- var objMtlMissing = 'unit/assets/objMtlMissing.obj';
- var validSTLfileWithoutExtension = 'unit/assets/ascii';
-
- testSketchWithPromise('error prevents sketch continuing', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadModel(invalidFile);
- setTimeout(resolve, 50);
- };
-
- sketch.setup = function() {
- reject(new Error('Setup called'));
- };
-
- sketch.draw = function() {
- reject(new Error('Draw called'));
- };
+import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
+import loading from '../../../src/webgl/loading';
+import { Geometry } from '../../../src/webgl/p5.Geometry';
+
+suite('loadModel', function() {
+ const invalidFile = '404file';
+ const validFile = '/test/unit/assets/teapot.obj';
+ const validObjFileforMtl = '/test/unit/assets/octa-color.obj';
+ const validSTLfile = '/test/unit/assets/ascii.stl';
+ const inconsistentColorObjFile = '/test/unit/assets/eg1.obj';
+ const objMtlMissing = '/test/unit/assets/objMtlMissing.obj';
+ const validSTLfileWithoutExtension = '/test/unit/assets/ascii';
+
+ beforeAll(async () => {
+ loading(mockP5, mockP5Prototype);
+ await httpMock.start({ quiet: true });
});
- testSketchWithPromise('error callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadModel(
- invalidFile,
- function() {
- reject(new Error('Success callback executed.'));
- },
- function() {
- // Wait a bit so that if both callbacks are executed we will get an error.
- setTimeout(resolve, 50);
- }
- );
- };
+ test('throws error when encountering HTTP errors', async () => {
+ await expect(mockP5Prototype.loadModel(invalidFile))
+ .rejects
+ .toThrow('Not Found');
});
- testSketchWithPromise('loading correctly triggers setup', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadModel(validFile);
- };
-
- sketch.setup = function() {
- resolve();
- };
+ test('error callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadModel(invalidFile, () => {
+ reject("Success callback executed");
+ }, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ });
+ });
});
- testSketchWithPromise('success callback is called', function(
- sketch,
- done
- ) {
- var hasBeenCalled = false;
- sketch.preload = function() {
- sketch.loadModel(
- validFile,
- function() {
- hasBeenCalled = true;
- done();
- },
- function(err) {
- done(new Error('Error callback was entered: ' + err));
- }
- );
- };
-
- sketch.setup = function() {
- if (!hasBeenCalled) {
- done(new Error('Setup called prior to success callback'));
- }
- };
+ test('success callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadModel(validFile, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ }, (err) => {
+ reject(`Error callback called: ${err.toString()}`);
+ });
+ });
});
test('loads OBJ file with associated MTL file correctly', async function(){
- const model = await promisedSketch(function (sketch,resolve,reject){
- sketch.preload=function(){
- sketch.loadModel(validObjFileforMtl,resolve,reject);
- };
- });
- const expectedColors=[
- 0, 0, 0.5,
- 0, 0, 0.5,
- 0, 0, 0.5,
- 0, 0, 0.942654,
- 0, 0, 0.942654,
- 0, 0, 0.942654,
- 0, 0.815632, 1,
- 0, 0.815632, 1,
- 0, 0.815632, 1,
- 0, 0.965177, 1,
- 0, 0.965177, 1,
- 0, 0.965177, 1,
- 0.848654, 1, 0.151346,
- 0.848654, 1, 0.151346,
- 0.848654, 1, 0.151346,
- 1, 0.888635, 0,
- 1, 0.888635, 0,
- 1, 0.888635, 0,
- 1, 0.77791, 0,
- 1, 0.77791, 0,
- 1, 0.77791, 0,
- 0.5, 0, 0,
- 0.5, 0, 0,
- 0.5, 0, 0
+ const model = await mockP5Prototype.loadModel(validObjFileforMtl);
+
+ const expectedColors = [
+ 0, 0, 0.5, 1,
+ 0, 0, 0.5, 1,
+ 0, 0, 0.5, 1,
+ 0, 0, 0.942654, 1,
+ 0, 0, 0.942654, 1,
+ 0, 0, 0.942654, 1,
+ 0, 0.815632, 1, 1,
+ 0, 0.815632, 1, 1,
+ 0, 0.815632, 1, 1,
+ 0, 0.965177, 1, 1,
+ 0, 0.965177, 1, 1,
+ 0, 0.965177, 1, 1,
+ 0.848654, 1, 0.151346, 1,
+ 0.848654, 1, 0.151346, 1,
+ 0.848654, 1, 0.151346, 1,
+ 1, 0.888635, 0, 1,
+ 1, 0.888635, 0, 1,
+ 1, 0.888635, 0, 1,
+ 1, 0.77791, 0, 1,
+ 1, 0.77791, 0, 1,
+ 1, 0.77791, 0, 1,
+ 0.5, 0, 0, 1,
+ 0.5, 0, 0, 1,
+ 0.5, 0, 0, 1
];
- assert.deepEqual(model.vertexColors,expectedColors);
+
+ assert.deepEqual(model.vertexColors, expectedColors);
});
+
test('inconsistent vertex coloring throws error', async function() {
// Attempt to load the model and catch the error
- let errorCaught = null;
- try {
- await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadModel(inconsistentColorObjFile, resolve, reject);
- };
- });
- } catch (error) {
- errorCaught = error;
- }
-
- // Assert that an error was caught and that it has the expected message
- assert.instanceOf(errorCaught, Error, 'No error thrown for inconsistent vertex coloring');
- assert.equal(errorCaught.message, 'Model coloring is inconsistent. Either all vertices should have colors or none should.', 'Unexpected error message for inconsistent vertex coloring');
+ await expect(mockP5Prototype.loadModel(inconsistentColorObjFile))
+ .rejects
+ .toThrow('Model coloring is inconsistent. Either all vertices should have colors or none should.');
});
test('missing MTL file shows OBJ model without vertexColors', async function() {
- const model = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadModel(objMtlMissing, resolve, reject);
- };
- });
- assert.instanceOf(model, p5.Geometry);
+ const model = await mockP5Prototype.loadModel(objMtlMissing);
+ assert.instanceOf(model, Geometry);
assert.equal(model.vertexColors.length, 0, 'Model should not have vertex colors');
});
test('returns an object with correct data', async function() {
- const model = await promisedSketch(function(sketch, resolve, reject) {
- var _model;
- sketch.preload = function() {
- _model = sketch.loadModel(validFile, function() {}, reject);
- };
-
- sketch.setup = function() {
- resolve(_model);
- };
- });
- assert.instanceOf(model, p5.Geometry);
+ const model = await mockP5Prototype.loadModel(validFile);
+ assert.instanceOf(model, Geometry);
});
test('passes an object with correct data to callback', async function() {
- const model = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadModel(validFile, resolve, reject);
- };
+ await mockP5Prototype.loadModel(validFile, (model) => {
+ assert.instanceOf(model, Geometry);
});
- assert.instanceOf(model, p5.Geometry);
});
test('resolves STL file correctly', async function() {
- const model = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadModel(validSTLfile, resolve, reject);
- };
- });
- assert.instanceOf(model, p5.Geometry);
+ const model = await mockP5Prototype.loadModel(validSTLfile);
+ assert.instanceOf(model, Geometry);
});
test('resolves STL file correctly with explicit extension', async function() {
- const model = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadModel(validSTLfileWithoutExtension, resolve, reject, '.stl');
- };
- });
- assert.instanceOf(model, p5.Geometry);
+ const model = await mockP5Prototype.loadModel(validSTLfileWithoutExtension, '.stl');
+ assert.instanceOf(model, Geometry);
});
test('resolves STL file correctly with case insensitive extension', async function() {
- const model = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadModel(validSTLfileWithoutExtension, resolve, reject, '.STL');
- };
- });
- assert.instanceOf(model, p5.Geometry);
+ const model = await mockP5Prototype.loadModel(validSTLfileWithoutExtension, '.STL');
+ assert.instanceOf(model, Geometry);
});
});
diff --git a/test/unit/io/loadShader.js b/test/unit/io/loadShader.js
index 0351a0ba63..51aef19716 100644
--- a/test/unit/io/loadShader.js
+++ b/test/unit/io/loadShader.js
@@ -1,162 +1,70 @@
-import p5 from '../../../src/app.js';
-import { testSketchWithPromise, promisedSketch } from '../../js/p5_helpers';
-
-suite.todo('loadShader', function() {
- var invalidFile = '404file';
- var vertFile = 'unit/assets/vert.glsl';
- var fragFile = 'unit/assets/frag.glsl';
-
- testSketchWithPromise('error with vert prevents sketch continuing', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadShader(invalidFile, fragFile);
- setTimeout(resolve, 50);
- };
-
- sketch.setup = function() {
- reject(new Error('Setup called'));
- };
-
- sketch.draw = function() {
- reject(new Error('Draw called'));
- };
+import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
+import material from '../../../src/webgl/material';
+import { Shader } from '../../../src/webgl/p5.Shader';
+
+suite('loadShader', function() {
+ const invalidFile = '404file';
+ const vertFile = '/test/unit/assets/vert.glsl';
+ const fragFile = '/test/unit/assets/frag.glsl';
+
+ beforeAll(async () => {
+ material(mockP5, mockP5Prototype);
+ await httpMock.start({ quiet: true });
});
- testSketchWithPromise('error with frag prevents sketch continuing', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadShader(vertFile, invalidFile);
- setTimeout(resolve, 50);
- };
-
- sketch.setup = function() {
- reject(new Error('Setup called'));
- };
-
- sketch.draw = function() {
- reject(new Error('Draw called'));
- };
+ test('throws error when encountering HTTP errors in vert shader', async () => {
+ await expect(mockP5Prototype.loadShader(invalidFile, fragFile))
+ .rejects
+ .toThrow('Not Found');
});
- testSketchWithPromise('error callback is called for vert', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadShader(
- invalidFile,
- fragFile,
- function() {
- reject(new Error('Success callback executed.'));
- },
- function() {
- // Wait a bit so that if both callbacks are executed we will get an error.
- setTimeout(resolve, 50);
- }
- );
- };
+ test('throws error when encountering HTTP errors in frag shader', async () => {
+ await expect(mockP5Prototype.loadShader(vertFile, invalidFile))
+ .rejects
+ .toThrow('Not Found');
});
- testSketchWithPromise('error callback is called for frag', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadShader(
- vertFile,
- invalidFile,
- function() {
- reject(new Error('Success callback executed.'));
- },
- function() {
- // Wait a bit so that if both callbacks are executed we will get an error.
- setTimeout(resolve, 50);
- }
- );
- };
+ test('error callback is called for vert shader', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadShader(invalidFile, fragFile, () => {
+ reject("Success callback executed");
+ }, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ });
+ });
});
- testSketchWithPromise('loading correctly triggers setup', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadShader(vertFile, fragFile);
- };
-
- sketch.setup = function() {
- resolve();
- };
+ test('error callback is called for frag shader', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadShader(vertFile, invalidFile, () => {
+ reject("Success callback executed");
+ }, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ });
+ });
});
- testSketchWithPromise('success callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- var hasBeenCalled = false;
- sketch.preload = function() {
- sketch.loadShader(
- vertFile,
- fragFile,
- function() {
- hasBeenCalled = true;
- },
- function(err) {
- reject(new Error('Error callback was entered: ' + err));
- }
- );
- };
-
- sketch.setup = function() {
- if (!hasBeenCalled) {
- reject(new Error('Setup called prior to success callback'));
- } else {
+ test('success callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadShader(vertFile, fragFile, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
setTimeout(resolve, 50);
- }
- };
+ }, (err) => {
+ reject(`Error callback called: ${err.toString()}`);
+ });
+ });
});
test('returns an object with correct data', async function() {
- const shader = await promisedSketch(function(sketch, resolve, reject) {
- var _shader;
- sketch.preload = function() {
- _shader = sketch.loadShader(vertFile, fragFile, function() {}, reject);
- };
-
- sketch.setup = function() {
- resolve(_shader);
- };
- });
- assert.instanceOf(shader, p5.Shader);
+ const shader = await mockP5Prototype.loadShader(vertFile, fragFile);
+ assert.instanceOf(shader, Shader);
});
test('passes an object with correct data to callback', async function() {
- const model = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadShader(vertFile, fragFile, resolve, reject);
- };
- });
- assert.instanceOf(model, p5.Shader);
- });
-
- test('does not run setup after complete when called outside of preload', async function() {
- let setupCallCount = 0;
- await promisedSketch(function(sketch, resolve, reject) {
- sketch.setup = function() {
- setupCallCount++;
- sketch.loadShader(vertFile, fragFile, resolve, reject);
- };
+ await mockP5Prototype.loadShader(vertFile, fragFile, (shader) => {
+ assert.instanceOf(shader, Shader);
});
- assert.equal(setupCallCount, 1);
});
});
diff --git a/test/unit/io/loadStrings.js b/test/unit/io/loadStrings.js
index c3e3ae7d30..b8db23cb53 100644
--- a/test/unit/io/loadStrings.js
+++ b/test/unit/io/loadStrings.js
@@ -1,124 +1,70 @@
-import { testSketchWithPromise, promisedSketch } from '../../js/p5_helpers';
+import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
+import files from '../../../src/io/files';
-suite.todo('loadStrings', function() {
+suite('loadStrings', function() {
const invalidFile = '404file';
- const validFile = 'unit/assets/sentences.txt';
- const fileWithEmptyLines = 'unit/assets/empty_lines.txt';
- const fileWithManyLines = 'unit/assets/many_lines.txt';
+ const validFile = '/test/unit/assets/sentences.txt';
+ const fileWithEmptyLines = '/test/unit/assets/empty_lines.txt';
+ const fileWithManyLines = '/test/unit/assets/many_lines.txt';
- testSketchWithPromise('error prevents sketch continuing', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadStrings(invalidFile, reject, function() {
- setTimeout(resolve, 50);
- });
- };
-
- sketch.setup = function() {
- reject(new Error('Entered setup'));
- };
-
- sketch.draw = function() {
- reject(new Error('Entered draw'));
- };
+ beforeAll(async () => {
+ files(mockP5, mockP5Prototype);
+ await httpMock.start({ quiet: true });
});
- testSketchWithPromise('error callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadStrings(
- invalidFile,
- function() {
- reject(new Error('Success callback executed.'));
- },
- function() {
- // Wait a bit so that if both callbacks are executed we will get an error.
- setTimeout(resolve, 50);
- }
- );
- };
+ test('throws error when encountering HTTP errors', async () => {
+ await expect(mockP5Prototype.loadStrings(invalidFile))
+ .rejects
+ .toThrow('Not Found');
});
- testSketchWithPromise('loading correctly triggers setup', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadStrings(validFile);
- };
-
- sketch.setup = resolve();
- });
-
- testSketchWithPromise('success callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadStrings(validFile, resolve, function(err) {
- reject(new Error('Error callback was entered: ' + err));
+ test('error callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadStrings(invalidFile, () => {
+ reject("Success callback executed");
+ }, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
});
- };
-
- sketch.setup = function() {
- reject(new Error('Setup called prior to success callback'));
- };
+ });
});
- test('returns an array of strings', async function() {
- const strings = await promisedSketch(function(sketch, resolve, reject) {
- let strings;
- sketch.preload = function() {
- strings = sketch.loadStrings(validFile, function() {}, reject);
- };
-
- sketch.setup = function() {
- resolve(strings);
- };
+ test('success callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadStrings(validFile, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ }, (err) => {
+ reject(`Error callback called: ${err.toString()}`);
+ });
});
+ });
+ test('returns an array of strings', async () => {
+ const strings = await mockP5Prototype.loadStrings(validFile);
assert.isArray(strings);
- for (let i = 0; i < strings.length; i++) {
- assert.isString(strings[i]);
+ for(let string of strings){
+ assert.isString(string);
}
});
- test('passes an array to success callback', async function() {
- const strings = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadStrings(validFile, resolve, reject);
- };
+ test('passes an array to success callback', async () => {
+ await mockP5Prototype.loadStrings(validFile, (strings) => {
+ assert.isArray(strings);
+ for(let string of strings){
+ assert.isString(string);
+ }
});
- assert.isArray(strings);
- for (let i = 0; i < strings.length; i++) {
- assert.isString(strings[i]);
- }
});
- test('should include empty strings', async function() {
- const strings = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadStrings(fileWithEmptyLines, resolve, reject);
- };
- });
+ test('should include empty strings', async () => {
+ const strings = await mockP5Prototype.loadStrings(fileWithEmptyLines);
assert.isArray(strings, 'Array passed to callback function');
assert.lengthOf(strings, 6, 'length of data is 6');
});
- test('can load file with many lines', async function() {
- const strings = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadStrings(fileWithManyLines, resolve, reject);
- };
- });
+ test('can load file with many lines', async () => {
+ const strings = await mockP5Prototype.loadStrings(fileWithManyLines);
assert.isArray(strings, 'Array passed to callback function');
assert.lengthOf(strings, 131073, 'length of data is 131073');
});
diff --git a/test/unit/io/loadTable.js b/test/unit/io/loadTable.js
index d5c378033c..de2b52803b 100644
--- a/test/unit/io/loadTable.js
+++ b/test/unit/io/loadTable.js
@@ -1,168 +1,90 @@
-import { testSketchWithPromise, promisedSketch } from '../../js/p5_helpers';
-
-suite.todo('loadTable', function() {
- var invalidFile = '404file';
- var validFile = 'unit/assets/csv.csv';
-
- testSketchWithPromise('error prevents sketch continuing', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadTable(invalidFile);
- setTimeout(resolve, 50);
- };
-
- sketch.setup = function() {
- reject(new Error('Setup called'));
- };
-
- sketch.draw = function() {
- reject(new Error('Draw called'));
- };
+import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
+import files from '../../../src/io/files';
+import table from '../../../src/io/p5.Table';
+import tableRow from '../../../src/io/p5.TableRow';
+
+suite('loadTable', function() {
+ const invalidFile = '404file';
+ const validFile = '/test/unit/assets/csv.csv';
+
+ beforeAll(async () => {
+ files(mockP5, mockP5Prototype);
+ table(mockP5, mockP5Prototype);
+ tableRow(mockP5, mockP5Prototype);
+ await httpMock.start({ quiet: true });
});
- testSketchWithPromise('error callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadTable(
- invalidFile,
- function() {
- reject(new Error('Success callback executed.'));
- },
- function() {
- // Wait a bit so that if both callbacks are executed we will get an error.
- setTimeout(resolve, 50);
- }
- );
- };
+ test('throws error when encountering HTTP errors', async () => {
+ await expect(mockP5Prototype.loadTable(invalidFile))
+ .rejects
+ .toThrow('Not Found');
});
- testSketchWithPromise('loading correctly triggers setup', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadTable(validFile);
- };
-
- sketch.setup = function() {
- resolve();
- };
+ test('error callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadTable(invalidFile, () => {
+ reject("Success callback executed");
+ }, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ });
+ });
});
- testSketchWithPromise('success callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- var hasBeenCalled = false;
- sketch.preload = function() {
- sketch.loadTable(
- validFile,
- function() {
- hasBeenCalled = true;
- },
- function(err) {
- reject(new Error('Error callback was entered: ' + err));
- }
- );
- };
-
- sketch.setup = function() {
- if (!hasBeenCalled) {
- reject(new Error('Setup called prior to success callback'));
- } else {
+ test('success callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadTable(validFile, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
setTimeout(resolve, 50);
- }
- };
+ }, (err) => {
+ reject(`Error callback called: ${err.toString()}`);
+ });
+ });
});
- test('returns an object with correct data', async function() {
- const table = await promisedSketch(function(sketch, resolve, reject) {
- let _table;
- sketch.preload = function() {
- _table = sketch.loadTable(validFile, function() {}, reject);
- };
-
- sketch.setup = function() {
- resolve(_table);
- };
- });
- assert.equal(table.getRowCount(), 4);
+ test('returns an object with correct data', async () => {
+ const table = await mockP5Prototype.loadTable(validFile);
+ assert.equal(table.getRowCount(), 5);
assert.strictEqual(table.getRow(1).getString(0), 'David');
assert.strictEqual(table.getRow(1).getNum(1), 31);
});
- test('passes an object to success callback for object JSON', async function() {
- const table = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadTable(validFile, resolve, reject);
- };
+ test('passes an object with correct data to success callback', async () => {
+ await mockP5Prototype.loadTable(validFile, (table) => {
+ assert.equal(table.getRowCount(), 5);
+ assert.strictEqual(table.getRow(1).getString(0), 'David');
+ assert.strictEqual(table.getRow(1).getNum(1), 31);
});
- assert.equal(table.getRowCount(), 4);
- assert.strictEqual(table.getRow(1).getString(0), 'David');
- assert.strictEqual(table.getRow(1).getNum(1), 31);
});
- test('csv option returns the correct data', async function() {
- const table = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadTable(validFile, 'csv', resolve, reject);
- };
- });
- assert.equal(table.getRowCount(), 4);
+ test('separator option returns the correct data', async () => {
+ const table = await mockP5Prototype.loadTable(validFile, ',');
+ assert.equal(table.getRowCount(), 5);
assert.strictEqual(table.getRow(1).getString(0), 'David');
assert.strictEqual(table.getRow(1).getNum(1), 31);
});
- test('using the header option works', async function() {
- const table = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadTable(validFile, 'header', resolve, reject);
- };
- });
- assert.equal(table.getRowCount(), 3);
- assert.strictEqual(table.getRow(0).getString('name'), 'David');
- assert.strictEqual(table.getRow(0).getNum('age'), 31);
- });
-
- test('allows the csv and header options together', async function() {
- const table = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadTable(validFile, 'csv', 'header', resolve, reject);
- };
- });
- assert.equal(table.getRowCount(), 3);
- assert.strictEqual(table.getRow(0).getString('name'), 'David');
- assert.strictEqual(table.getRow(0).getNum('age'), 31);
+ test('using the header option works', async () => {
+ const table = await mockP5Prototype.loadTable(validFile, ',', true);
+ assert.equal(table.getRowCount(), 4);
+ assert.strictEqual(table.getRow(0).getString(0), 'David');
+ assert.strictEqual(table.getRow(0).getNum(1), 31);
});
- test('CSV files should handle commas within quoted fields', async function() {
- const table = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadTable(validFile, resolve, reject);
- };
- });
- assert.equal(table.getRowCount(), 4);
+ test.todo('CSV files should handle commas within quoted fields', async () => {
+ // TODO: Current parsing does not handle quoted fields
+ const table = await mockP5Prototype.loadTable(validFile);
+ assert.equal(table.getRowCount(), 5);
assert.equal(table.getRow(2).get(0), 'David, Jr.');
assert.equal(table.getRow(2).getString(0), 'David, Jr.');
assert.equal(table.getRow(2).get(1), '11');
assert.equal(table.getRow(2).getString(1), 11);
});
- test('CSV files should handle escaped quotes and returns within quoted fields', async function() {
- const table = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadTable(validFile, resolve, reject);
- };
- });
- assert.equal(table.getRowCount(), 4);
+ test.todo('CSV files should handle escaped quotes and returns within quoted fields', async () => {
+ // TODO: Current parsing does not handle quoted fields
+ const table = await mockP5Prototype.loadTable(validFile);
+ assert.equal(table.getRowCount(), 5);
assert.equal(table.getRow(3).get(0), 'David,\nSr. "the boss"');
});
});
diff --git a/test/unit/io/loadXML.js b/test/unit/io/loadXML.js
index 317709b809..9ed8109e68 100644
--- a/test/unit/io/loadXML.js
+++ b/test/unit/io/loadXML.js
@@ -1,112 +1,58 @@
-import { testSketchWithPromise, promisedSketch } from '../../js/p5_helpers';
-
-suite.todo('loadXML', function() {
- var invalidFile = '404file';
- var validFile = 'unit/assets/books.xml';
-
- testSketchWithPromise('error prevents sketch continuing', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadXML(invalidFile);
- setTimeout(resolve, 50);
- };
-
- sketch.setup = function() {
- reject(new Error('Setup called'));
- };
-
- sketch.draw = function() {
- reject(new Error('Draw called'));
- };
+import { mockP5, mockP5Prototype, httpMock } from '../../js/mocks';
+import files from '../../../src/io/files';
+import xml from '../../../src/io/p5.XML';
+
+suite('loadXML', function() {
+ const invalidFile = '404file';
+ const validFile = '/test/unit/assets/books.xml';
+
+ beforeAll(async () => {
+ files(mockP5, mockP5Prototype);
+ xml(mockP5, mockP5Prototype);
+ await httpMock.start({ quiet: true });
});
- testSketchWithPromise('error callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadXML(
- invalidFile,
- function() {
- reject(new Error('Success callback executed.'));
- },
- function() {
- // Wait a bit so that if both callbacks are executed we will get an error.
- setTimeout(resolve, 50);
- }
- );
- };
+ test('throws error when encountering HTTP errors', async () => {
+ await expect(mockP5Prototype.loadXML(invalidFile))
+ .rejects
+ .toThrow('Not Found');
});
- testSketchWithPromise('loading correctly triggers setup', function(
- sketch,
- resolve,
- reject
- ) {
- sketch.preload = function() {
- sketch.loadXML(validFile);
- };
-
- sketch.setup = function() {
- resolve();
- };
+ test('error callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadXML(invalidFile, () => {
+ console.log("here");
+ reject("Success callback executed");
+ }, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
+ setTimeout(resolve, 50);
+ });
+ });
});
- testSketchWithPromise('success callback is called', function(
- sketch,
- resolve,
- reject
- ) {
- var hasBeenCalled = false;
- sketch.preload = function() {
- sketch.loadXML(
- validFile,
- function() {
- hasBeenCalled = true;
- },
- function(err) {
- reject(new Error('Error callback was entered: ' + err));
- }
- );
- };
-
- sketch.setup = function() {
- if (!hasBeenCalled) {
- reject(new Error('Setup called prior to success callback'));
- } else {
+ test('success callback is called', async () => {
+ await new Promise((resolve, reject) => {
+ mockP5Prototype.loadXML(validFile, () => {
+ // Wait a bit so that if both callbacks are executed we will get an error.
setTimeout(resolve, 50);
- }
- };
+ }, (err) => {
+ reject(`Error callback called: ${err.toString()}`);
+ });
+ });
});
- test('returns an object with correct data', async function() {
- const xml = await promisedSketch(function(sketch, resolve, reject) {
- let _xml;
- sketch.preload = function() {
- _xml = sketch.loadXML(validFile, function() {}, reject);
- };
-
- sketch.setup = function() {
- resolve(_xml);
- };
- });
+ test('returns an object with correct data', async () => {
+ const xml = await mockP5Prototype.loadXML(validFile);
assert.isObject(xml);
- var children = xml.getChildren('book');
+ const children = xml.getChildren('book');
assert.lengthOf(children, 12);
});
- test('passes an object with correct data', async function() {
- const xml = await promisedSketch(function(sketch, resolve, reject) {
- sketch.preload = function() {
- sketch.loadXML(validFile, resolve, reject);
- };
+ test('passes an object with correct data to success callback', async () => {
+ await mockP5Prototype.loadXML(validFile, (xml) => {
+ assert.isObject(xml);
+ const children = xml.getChildren('book');
+ assert.lengthOf(children, 12);
});
- assert.isObject(xml);
- var children = xml.getChildren('book');
- assert.lengthOf(children, 12);
});
});
diff --git a/test/unit/io/saveModel.js b/test/unit/io/saveModel.js
deleted file mode 100644
index 8cd0009ba1..0000000000
--- a/test/unit/io/saveModel.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import p5 from '../../../src/app.js';
-import { testWithDownload } from '../../js/p5_helpers';
-
-suite.todo('saveModel',function() {
- var myp5;
-
- beforeAll(function(done) {
- new p5(function(p) {
- p.setup = function() {
- myp5 = p;
- done();
- };
- });
- });
-
- afterAll(function() {
- myp5.remove();
- });
-
- testWithDownload(
- 'should download an .obj file with expected contents',
- async function(blobContainer) {
- //.obj content as a string
- const objContent = `v 100 0 0
- v 0 -100 0
- v 0 0 -100
- v 0 100 0
- v 100 0 0
- v 0 0 -100
- v 0 100 0
- v 0 0 100
- v 100 0 0
- v 0 100 0
- v 0 0 -100
- v -100 0 0
- v -100 0 0
- v 0 -100 0
- v 0 0 100
- v 0 0 -100
- v 0 -100 0
- v -100 0 0
- v 0 100 0
- v -100 0 0
- v 0 0 100
- v 0 0 100
- v 0 -100 0
- v 100 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vt 0 0
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- vn 0 0 1
- f 1 2 3
- f 4 5 6
- f 7 8 9
- f 10 11 12
- f 13 14 15
- f 16 17 18
- f 19 20 21
- f 22 23 24
- `;
-
- const objBlob = new Blob([objContent], { type: 'text/plain' });
-
- myp5.downloadFile(objBlob, 'model', 'obj');
-
- let myBlob = blobContainer.blob;
-
- let text = await myBlob.text();
-
- assert.strictEqual(text, objContent);
- },
- true
- );
-});
diff --git a/test/unit/io/saveTable.js b/test/unit/io/saveTable.js
index f6abb05d56..fb41726dab 100644
--- a/test/unit/io/saveTable.js
+++ b/test/unit/io/saveTable.js
@@ -1,94 +1,92 @@
-import p5 from '../../../src/app.js';
-import { testWithDownload } from '../../js/p5_helpers';
+import { mockP5, mockP5Prototype } from '../../js/mocks';
+import * as fileSaver from 'file-saver';
+import { vi } from 'vitest';
+import files from '../../../src/io/files';
+import table from '../../../src/io/p5.Table';
+import tableRow from '../../../src/io/p5.TableRow';
-suite.todo('saveTable', function() {
- let validFile = 'unit/assets/csv.csv';
- let myp5;
+vi.mock('file-saver');
+
+suite('saveTable', function() {
+ const validFile = '/test/unit/assets/csv.csv';
let myTable;
- beforeAll(function() {
- new p5(function(p) {
- p.setup = function() {
- myp5 = p;
- };
- });
+ beforeAll(async function() {
+ files(mockP5, mockP5Prototype);
+ table(mockP5, mockP5Prototype);
+ tableRow(mockP5, mockP5Prototype);
+ myTable = await mockP5Prototype.loadTable(validFile, 'csv', 'header');
});
- afterAll(function() {
- myp5.remove();
+ afterEach(() => {
+ vi.clearAllMocks();
});
- beforeEach(async function loadMyTable() {
- await new Promise(resolve => {
- myp5.loadTable(validFile, 'csv', 'header', function(table) {
- myTable = table;
- resolve();
- });
- });
+ test('should be a function', function() {
+ assert.ok(mockP5Prototype.saveTable);
+ assert.typeOf(mockP5Prototype.saveTable, 'function');
});
- test('should be a function', function() {
- assert.ok(myp5.saveTable);
- assert.typeOf(myp5.saveTable, 'function');
+ test('should download a file with expected contents', async () => {
+ mockP5Prototype.saveTable(myTable, 'filename');
+
+ // TODO: Need comprehensive way to compare blobs in spy call
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs)
+ .toHaveBeenCalledWith(
+ expect.any(Blob),
+ 'filename.csv'
+ );
});
- testWithDownload(
- 'should download a file with expected contents',
- async function(blobContainer) {
- myp5.saveTable(myTable, 'filename');
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- let myTableStr = myTable.columns.join(',') + '\n';
- for (let i = 0; i < myTable.rows.length; i++) {
- myTableStr += myTable.rows[i].arr.join(',') + '\n';
- }
+ test('should download a file with expected contents (tsv)', async () => {
+ mockP5Prototype.saveTable(myTable, 'filename', 'tsv');
- assert.strictEqual(text, myTableStr);
- },
- true
- );
+ // TODO: Need comprehensive way to compare blobs in spy call
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs)
+ .toHaveBeenCalledWith(
+ expect.any(Blob),
+ 'filename.tsv'
+ );
+ });
- testWithDownload(
- 'should download a file with expected contents (tsv)',
- async function(blobContainer) {
- myp5.saveTable(myTable, 'filename', 'tsv');
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- let myTableStr = myTable.columns.join('\t') + '\n';
- for (let i = 0; i < myTable.rows.length; i++) {
- myTableStr += myTable.rows[i].arr.join('\t') + '\n';
- }
- assert.strictEqual(text, myTableStr);
- },
- true
- );
+ test('should download a file with expected contents (html)', async () => {
+ mockP5Prototype.saveTable(myTable, 'filename', 'html');
- testWithDownload(
- 'should download a file with expected contents (html)',
- async function(blobContainer) {
- myp5.saveTable(myTable, 'filename', 'html');
- let myBlob = blobContainer.blob;
- let text = await myBlob.text();
- let domparser = new DOMParser();
- let htmldom = domparser.parseFromString(text, 'text/html');
- let trs = htmldom.querySelectorAll('tr');
- for (let i = 0; i < trs.length; i++) {
- let tds = trs[i].querySelectorAll('td');
- for (let j = 0; j < tds.length; j++) {
- // saveTable generates an HTML file with indentation spaces and line-breaks. The browser ignores these
- // while displaying. But they will still remain a part of the parsed DOM and hence must be removed.
- // More info at: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
- let tdText = tds[j].innerHTML.trim().replace(/\n/g, '');
- let tbText;
- if (i === 0) {
- tbText = myTable.columns[j].trim().replace(/\n/g, '');
- } else {
- tbText = myTable.rows[i - 1].arr[j].trim().replace(/\n/g, '');
- }
- assert.strictEqual(tdText, tbText);
- }
- }
- },
- true
- );
+ expect(fileSaver.saveAs).toHaveBeenCalledTimes(1);
+ expect(fileSaver.saveAs)
+ .toHaveBeenCalledWith(
+ expect.any(Blob),
+ 'filename.html'
+ );
+ });
+ // testWithDownload(
+ // 'should download a file with expected contents (html)',
+ // async function(blobContainer) {
+ // myp5.saveTable(myTable, 'filename', 'html');
+ // let myBlob = blobContainer.blob;
+ // let text = await myBlob.text();
+ // let domparser = new DOMParser();
+ // let htmldom = domparser.parseFromString(text, 'text/html');
+ // let trs = htmldom.querySelectorAll('tr');
+ // for (let i = 0; i < trs.length; i++) {
+ // let tds = trs[i].querySelectorAll('td');
+ // for (let j = 0; j < tds.length; j++) {
+ // // saveTable generates an HTML file with indentation spaces and line-breaks. The browser ignores these
+ // // while displaying. But they will still remain a part of the parsed DOM and hence must be removed.
+ // // More info at: https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
+ // let tdText = tds[j].innerHTML.trim().replace(/\n/g, '');
+ // let tbText;
+ // if (i === 0) {
+ // tbText = myTable.columns[j].trim().replace(/\n/g, '');
+ // } else {
+ // tbText = myTable.rows[i - 1].arr[j].trim().replace(/\n/g, '');
+ // }
+ // assert.strictEqual(tdText, tbText);
+ // }
+ // }
+ // },
+ // true
+ // );
});
diff --git a/test/unit/visual/cases/webgl.js b/test/unit/visual/cases/webgl.js
index 7d3b6d020f..2a3059a204 100644
--- a/test/unit/visual/cases/webgl.js
+++ b/test/unit/visual/cases/webgl.js
@@ -117,7 +117,7 @@ visualSuite('WebGL', function() {
'Object with different texture coordinates per use of vertex keeps the coordinates intact',
async function(p5, screenshot) {
p5.createCanvas(50, 50, p5.WEBGL);
- const tex = await new Promise(resolve => p5.loadImage('/unit/assets/cat.jpg', resolve));
+ const tex = await p5.loadImage('/unit/assets/cat.jpg');
const cube = await new Promise(resolve => p5.loadModel('/unit/assets/cube-textures.obj', resolve));
cube.normalize();
p5.background(255);
@@ -230,7 +230,7 @@ visualSuite('WebGL', function() {
visualSuite('ShaderFunctionality', function() {
visualTest('FillShader', async (p5, screenshot) => {
p5.createCanvas(50, 50, p5.WEBGL);
- const img = await new Promise(resolve => p5.loadImage('/unit/assets/cat.jpg', resolve));
+ const img = await p5.loadImage('/unit/assets/cat.jpg');
const fillShader = p5.createShader(
`
attribute vec3 aPosition;
@@ -281,7 +281,7 @@ visualSuite('WebGL', function() {
visualTest('ImageShader', async (p5, screenshot) => {
p5.createCanvas(50, 50, p5.WEBGL);
- const img = await new Promise(resolve => p5.loadImage('/unit/assets/cat.jpg', resolve));
+ const img = await p5.loadImage('/unit/assets/cat.jpg');
const imgShader = p5.createShader(
`
precision mediump float;