Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TIMOB-23995] iOS: Add __dirname and __filename to the global app.js scope #10241

Merged
merged 4 commits into from
Aug 10, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions iphone/Classes/KrollBridge.m
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,16 @@ - (void)didStartNewContext:(KrollContext *)kroll
// Make the global object itself available under the name "global"
TiStringRef globalPropertyName = TiStringCreateWithCFString((CFStringRef) @"global");
TiObjectSetProperty(jsContext, globalRef, globalPropertyName, globalRef, kTiPropertyAttributeDontEnum | kTiPropertyAttributeReadOnly | kTiPropertyAttributeDontDelete, NULL);
TiStringRelease(globalPropertyName);

// Set the __dirname and __filename for the app.js.
// For other files, it will be injected via the `TitaniumModuleRequireFormat` property
TiStringRef dirnameProperty = TiStringCreateWithCFString((CFStringRef) @"__dirname");
TiStringRef filenameProperty = TiStringCreateWithCFString((CFStringRef) @"__filename");
TiObjectSetProperty(jsContext, globalRef, dirnameProperty, [KrollObject toValue:kroll value:@"/"], kTiPropertyAttributeDontEnum | kTiPropertyAttributeReadOnly | kTiPropertyAttributeDontDelete, NULL);
TiObjectSetProperty(jsContext, globalRef, filenameProperty, [KrollObject toValue:kroll value:@"/app.js"], kTiPropertyAttributeDontEnum | kTiPropertyAttributeReadOnly | kTiPropertyAttributeDontDelete, NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__filename should just be the filename, no /.

Copy link
Collaborator Author

@hansemannn hansemannn Aug 9, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's with parity of the current implementation (for require'ish format). Then we should change it for both.

EDIT: It looks like node uses the full (absolute) path, interesting. How should we go?

Node example:
index.js

console.log('__filename = ' + __filename);

> node index.js:

__filename = /Users/hknoechel/Documents/dev/test_node/index.js

TiStringRelease(dirnameProperty);
TiStringRelease(filenameProperty);

//if we have a preload dictionary, register those static key/values into our namespace
if (preload != nil) {
Expand Down
317 changes: 317 additions & 0 deletions tests/Resources/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
/*
* Appcelerator Titanium Mobile
* Copyright (c) 2011-Present by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Apache Public License
* Please see the LICENSE included with this distribution for details.
*/
/* eslint-env mocha */
/* global Ti, global */
/* eslint no-unused-expressions: "off", no-global-assign: "off", no-native-reassign: "off" */
'use strict';
var utilities,
win,
$results = [],
failed = false,
should;

require('./ti-mocha');
utilities = require('./utilities/utilities');
should = require('./utilities/assertions');

// ============================================================================
// Some tests to test the global scope of the app.js

// Must test global is available in first app.js explicitly!
// (since app.js is treated slightly differently than required files on at least Android)
describe('global', function () {
it('should be available as \'global\'', function () {
should(global).be.ok;
});
});

// Must have __dirname in the global scope, even in our app.js
describe('__dirname', function () {
it.ios('should be available as \'__dirname\'', function () {
should(__dirname).be.ok;
should(__dirname).be.a.String;
should(__dirname).be.eql('/');
});
});

// Must have __filename in the global scope, even in our app.js
describe('__filename', function () {
it.ios('should be available as \'__filename\'', function () {
should(__filename).be.ok;
should(__filename).be.a.String;
should(__filename).be.eql('/app.js');
});
});

// ============================================================================
// Add the tests here using "require"
// Global behavior (top-level timers, functions, types)
require('./console.test');
require('./error.test');
require('./global.test');
require('./require.test');
require('./string.test');
require('./timers.test');
// ES6 syntax/compatability tests
require('./es6.arrows.test');
require('./es6.class.test');
require('./es6.default.args.test');
require('./es6.import.test');
require('./es6.rest.args.test');
require('./es6.spread.args.test');
require('./es6.string.interpolation.test');
// Titanium APIs
require('./ti.accelerometer.test');
require('./ti.analytics.test');
require('./ti.android.notificationmanager.test');
require('./ti.android.service.test');
require('./ti.api.test');
require('./ti.app.test');
require('./ti.app.ios.searchquery.test');
require('./ti.app.properties.test');
require('./ti.app.windows.backgroundservice.test');
require('./ti.blob.test');
require('./ti.buffer.test');
require('./ti.calendar.calendar.test');
require('./ti.codec.test');
require('./ti.contacts.test');
require('./ti.contacts.group.test');
require('./ti.contacts.person.test');
require('./ti.database.test');
require('./ti.filesystem.test');
require('./ti.filesystem.file.test');
require('./ti.filesystem.filestream.test');
require('./ti.geolocation.test');
require('./ti.gesture.test');
require('./ti.locale.test');
require('./ti.map.test');
require('./ti.media.test');
require('./ti.media.audioplayer.test');
require('./ti.media.sound.test');
require('./ti.media.videoplayer.test');
require('./ti.network.test');
require('./ti.network.cookie.test');
require('./ti.network.httpclient.test');
require('./ti.network.socket.tcp.test');
require('./ti.network.socket.udp.test');
require('./ti.platform.test');
require('./ti.platform.displaycaps.test');
require('./ti.proxy.test');
require('./ti.stream.test');
require('./ti.test');
require('./ti.ui.test');
require('./ti.ui.2dmatrix.test');
require('./ti.ui.activityindicator.test');
require('./ti.ui.alertdialog.test');
require('./ti.ui.android.drawerlayout.test');
require('./ti.ui.button.test');
require('./ti.ui.constants.test');
require('./ti.ui.emaildialog.test');
require('./ti.ui.imageview.test');
require('./ti.ui.ios.test');
require('./ti.ui.ios.navigationwindow.test');
require('./ti.ui.ios.previewcontext.test');
require('./ti.ui.label.test');
require('./ti.ui.layout.test');
require('./ti.ui.listview.test');
require('./ti.ui.maskedimage.test');
require('./ti.ui.optiondialog.test');
require('./ti.ui.picker.test');
require('./ti.ui.progressbar.test');
require('./ti.ui.scrollableview.test');
require('./ti.ui.scrollview.test');
require('./ti.ui.searchbar.test');
require('./ti.ui.slider.test');
require('./ti.ui.switch.test');
require('./ti.ui.tab.test');
require('./ti.ui.tabgroup.test');
require('./ti.ui.tableview.test');
require('./ti.ui.textarea.test');
require('./ti.ui.textfield.test');
require('./ti.ui.toolbar.test');
require('./ti.ui.view.test');
require('./ti.ui.webview.test');
require('./ti.ui.window.test');
require('./ti.ui.windows.commandbar.test');
require('./ti.utils.test');
require('./ti.xml.test');

// Load in any of the files added to the test/Resources folder of the SDK repos

loadAddonTestFiles(Ti.Filesystem.resourcesDirectory);

function loadAddonTestFiles (name) {
var info = Ti.Filesystem.getFile(name);
if (!info) {
console.warn('could not load addon test files: ' + name);
return;
}
if (info.isDirectory()) {
info.getDirectoryListing().forEach(function (listing) {
loadAddonTestFiles(listing);
});
// Only load the test files
} else if (/\w+.addontest\.js$/i.test(info.name)) {
try {
require(name.replace(/.js$/, '')); // eslint-disable-line security/detect-non-literal-require
} catch (e) {
console.log(e);
}
}
}

// ============================================================================

/**
* To make Jenkins junit reporting happy, let's use anything up until '#'/'.' in
* suite names as the full "class name". Then concanetate the remainder with the test name.
* This should consolidate tests together under our API names like 'Ti.Buffer', with subsuites' tests
* just represented as separate tests (the sub-suite name gets prefixed to the test name)
* @param {string[]} suites stack of suite names
* @param {string} testTitle single test name
* @return {object}
*/
function suiteAndTitle(suites, testTitle) {
var i;
var char;
var index = -1;
var suiteName = '';
var newTestTitle = '';
for (i = 0; i < suites.length; i++) {
char = suites[i].charAt(0);
if (char === '.' || char === '#') {
index = i;
break;
}
}
if (index !== -1) {
suiteName = suites.slice(0, index).join('.');
newTestTitle = suites.slice(index).join(' ') + ' ' + testTitle;
} else {
suiteName = suites.join('.');
newTestTitle = testTitle;
}
return {
suite: suiteName,
title: newTestTitle
};
}

// add a special mocha reporter that will time each test run using
// our microsecond timer
function $Reporter(runner) {
var started,
suites = [];

runner.on('suite', function (suite) {
if (suite.title) {
suites.push(suite.title);
}
});

runner.on('suite end', function (suite) {
if (suite.title) {
suites.pop();
}
});

runner.on('test', function (test) {
Ti.API.info('!TEST_START: ' + test.title);
started = new Date().getTime();
});

runner.on('pending', function () {
// TODO Spit out something like !TEST_SKIP: ?
started = new Date().getTime(); // reset timer. pending/skipped tests basically start and end immediately
});

// 'pending' hook for skipped tests? Does 'pending', then immediate 'test end'. No 'test' event

runner.on('fail', function (test, err) {
test.err = err;
failed = true;
});

runner.on('test end', function (test) {
var tdiff = new Date().getTime() - started,
err = test.err,
fixedNames = suiteAndTitle(suites, test.title),
result = {
state: test.state || 'skipped',
duration: tdiff,
suite: fixedNames.suite,
title: fixedNames.title,
error: err,
message: ''
},
message,
stack,
index,
msg,
stringified;

if (err) {
message = err.message || '';
stack = err.stack || message;
index = stack.indexOf(message) + message.length;
msg = stack.slice(0, index);
// uncaught
if (err.uncaught) {
msg = 'Uncaught ' + msg;
}
result.message = msg;
// indent stack trace without msg
stack = stack.slice(index ? index + 1 : index).replace(/^/gm, ' ');
result.stack = stack;
}

stringified = JSON.stringify(result);
stringified = stringified.replace(/\\n/g, '\\n')
.replace(/\\'/g, '\\\'')
.replace(/\\"/g, '\\"')
.replace(/\\&/g, '\\&')
.replace(/\\r/g, '\\r')
.replace(/\\t/g, '\\t')
.replace(/\\b/g, '\\b')
.replace(/\\f/g, '\\f');
// remove non-printable and other non-valid JSON chars
stringified = stringified.replace(/[\u0000-\u0019]+/g, ''); // eslint-disable-line no-control-regex
Ti.API.info('!TEST_END: ' + stringified);
$results.push(result);
});
}

mocha.setup({
reporter: $Reporter,
quiet: true
});

if (utilities.isWindows()) {
if (Ti.App.Windows.requestExtendedExecution) {
Ti.App.Windows.requestExtendedExecution();
}
}

// Display a window to host the test and show the final result.
win = Ti.UI.createWindow({
backgroundColor: 'yellow',
keepScreenOn: true
});
win.addEventListener('open', function () {
// Start executing the test suite.
mocha.run(function () {
// We've finished executing all tests.
win.backgroundColor = failed ? 'red' : 'green';
Ti.API.info('!TEST_RESULTS_STOP!');
if (utilities.isWindows()) {
if (Ti.App.Windows.closeExtendedExecution) {
Ti.App.Windows.closeExtendedExecution();
}
}
});
});
win.open();