Merge pull request #2448 from cacaodev/CPViewController-async

Fixed: CPViewController isViewLoaded property in async mode
This commit is contained in:
Alexandre Wilhelm
2016-05-11 12:20:48 -07:00
21 changed files with 1826 additions and 8 deletions
+15 -8
View File
@@ -150,7 +150,7 @@ var CPViewControllerCachedCibs;
If you use Interface Builder to create your views, and you initialize the
controller using the initWithCibName:bundle: methods, then you MUST NOT override
this method.
@note When using this method, the cib loading system is synchronous.
See the loadViewWithCompletionHandler: method for an asynchronous loading.
*/
@@ -179,11 +179,11 @@ var CPViewControllerCachedCibs;
/*!
Loads asynchronously the cib and creates the view that the controller manages.
@param aHandler A function passing the loaded view as the first argument
and a network error or nil as the second argument: function(view, error).
@note If the view has already been loaded, the completion handler is run immediatly
@note If the view has already been loaded, the completion handler is run immediatly
and the process is synchronous.
*/
- (void)loadViewWithCompletionHandler:(Function/*(view, error)*/)aHandler
@@ -217,7 +217,7 @@ var CPViewControllerCachedCibs;
[CPViewControllerCachedCibs setObject:aCib forKey:_cibName];
[aCib instantiateCibWithExternalNameTable:_cibExternalNameTable];
aHandler(_view, nil);
[self _viewDidLoadWithCompletionHandler:aHandler];
}
else
{
@@ -228,13 +228,13 @@ var CPViewControllerCachedCibs;
else
{
[cib instantiateCibWithExternalNameTable:_cibExternalNameTable];
aHandler(_view, nil);
[self _viewDidLoadWithCompletionHandler:aHandler];
}
}
else
{
_view = [CPView new];
aHandler(_view, nil);
[self _viewDidLoadWithCompletionHandler:aHandler];
}
}
@@ -285,9 +285,16 @@ var CPViewControllerCachedCibs;
}
- (void)_viewDidLoad
{
[self _viewDidLoadWithCompletionHandler:function() {
[self viewDidLoad];
}];
}
- (void)_viewDidLoadWithCompletionHandler:(Function)aHandler
{
[self willChangeValueForKey:"isViewLoaded"];
[self viewDidLoad];
aHandler(_view, nil);
_isViewLoaded = YES;
[self didChangeValueForKey:"isViewLoaded"];
}
@@ -0,0 +1,46 @@
/*
* AppController.j
* CPViewControllerTest
*
* Created by You on May 9, 2016.
* Copyright 2016, Your Company All rights reserved.
*/
@import <Foundation/Foundation.j>
@import <AppKit/AppKit.j>
@implementation AppController : CPObject
{
@outlet CPWindow theWindow;
@outlet CPViewController viewController;
BOOL isViewLoaded;
}
- (void)applicationDidFinishLaunching:(CPNotification)aNotification
{
// This is called when the application is done loading.
}
- (void)awakeFromCib
{
// This is called when the cib is done loading.
// You can implement this method on any object instantiated from a Cib.
// It's a useful hook for setting up current UI values, and other things.
isViewLoaded = NO;
// In this case, we want the window from Cib to become our full browser window
[theWindow setFullPlatformWindow:YES];
}
- (IBAction)load:(id)sender
{
[viewController loadViewWithCompletionHandler:function(view, error)
{
[view setBackgroundColor:[CPColor redColor]];
[view setFrameOrigin:CGPointMake(100,100)];
[[theWindow contentView] addSubview:view];
}];
}
@end
@@ -0,0 +1,94 @@
/*
* Copyright (c) 2014 Nuage Networks
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
// Import this Categories from your application
// You can now user -(void)setCucappIdentifier: and -(CPString)cucappIdentifier
// to set and get your cucapp IDs.
// Then from a test, you can use it as a selector like //CPView[cucappIdentifier="my-button"]
@import <AppKit/CPResponder.j>
@import <AppKit/CPMenuItem.j>
@implementation CPResponder (cucappAdditions)
- (void)setCucappIdentifier:(CPString)anIdentifier
{
self.__cucappIdentifier = anIdentifier;
}
- (CPString)cucappIdentifier
{
return self.__cucappIdentifier;
}
@end
@implementation CPMenuItem (cucappAdditionsMenu)
- (void)setCucappIdentifier:(CPString)anIdentifier
{
[[self _menuItemView] setCucappIdentifier:anIdentifier];
}
- (CPString)cucappIdentifier
{
[[self _menuItemView] cucappIdentifier];
}
@end
function load_cucapp_CLI(path)
{
if (!path)
path = "Cucapp/lib/Cucumber.j"
try {
objj_importFile(path, true, function() {
[Cucumber stopCucumber];
CPLog.debug("Cucapp CLI has been well loaded");
_addition_cpapplication_send_event_method();
});
}
catch(e)
{
[CPException raise:CPInvalidArgumentException reason:@"Invalid path for the lib Cucumber"];
}
}
function load_cucapp_record(path)
{
if (!path)
path = "CuCapp+Record.j"
try {
objj_importFile(path, true, function() {
CPLog.debug("Cucapp record has been well loaded");
});
}
catch(e)
{
[CPException raise:CPInvalidArgumentException reason:@"Invalid path for the lib Cucapp+Record.j"];
}
}
@@ -0,0 +1,591 @@
@import <AppKit/CPApplication.j>
function start_record(path)
{
[CPWindow start_record];
}
function stop_record()
{
[CPWindow stop_record];
}
function save_record(fileName)
{
if (!fileName)
fileName = @"record"
var eventRecords = [CPWindow eventRecords],
JSONEvents = [];
for (var i = 0; i < [eventRecords count]; i++)
{
var eventRecord = eventRecords[i];
[JSONEvents addObject:[eventRecord objectToJSON]];
}
var pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(JSON.stringify(JSONEvents, null, 4)));
pom.setAttribute('download', fileName + ".json");
pom.click();
}
function play_record(file_name, cucumber_path)
{
if (!cucumber_path)
cucumber_path = path = "../../Cucapp/lib/Cucumber.j";
load_cucapp_CLI(cucumber_path);
setTimeout(function(){
_load_javascript_file(file_name)
},1000);
}
function _load_javascript_file(file_name)
{
var AJAX_req = new XMLHttpRequest();
AJAX_req.open( "GET", file_name, true );
AJAX_req.setRequestHeader("Content-type", "application/json");
AJAX_req.onreadystatechange = function()
{
if (AJAX_req.readyState == 4)
{
var recordingEvents = JSON.parse(AJAX_req.responseText);
for (var i = 0; i < [recordingEvents count]; i++)
{
var recordingEvent = JSON.parse(recordingEvents[i]),
type = recordingEvent["event"]["type"];
if (type == CPLeftMouseDown && i < [recordingEvents count] - 1)
{
var nextRecordingEvent = JSON.parse(recordingEvents[i + 1]),
nextType = nextRecordingEvent["event"]["type"];
if (nextType == CPMouseMoved)
{
for (var j = i; j < [recordingEvents count]; j++)
{
var tmpRecordingEvent = JSON.parse(recordingEvents[j]),
tmpType = tmpRecordingEvent["event"]["type"];
if (tmpType == CPLeftMouseUp)
{
setTimeout(_simulate_drag_event, recordingEvent["event"]["timestamp"] * 1000, recordingEvent, tmpRecordingEvent);
i = j;
break;
}
}
continue;
}
}
setTimeout(_simulate_event, recordingEvent["event"]["timestamp"] * 1000, recordingEvent);
}
}
}
AJAX_req.send();
}
function _simulate_drag_event(event1, event2)
{
var keyView1 = event1["keyView"],
valueView1 = event1["valueView"],
keyView2 = event2["keyView"],
valueView2 = event2["valueView"],
locationInWindow1 = CGPointMake(event1["event"]["locationInWindow"]["x"], event1["event"]["locationInWindow"]["y"]);
locationInWindow2 = CGPointMake(event2["event"]["locationInWindow"]["x"], event2["event"]["locationInWindow"]["y"]);
if (keyView1 && valueView1 && keyView2 && valueView2)
simulate_dragged_click_view_to_view(keyView1, valueView1, keyView2, valueView2);
else if (keyView1 && valueView1)
simulate_dragged_click_view_to_point(keyView1, valueView1, locationInWindow1.x, locationInWindow1.y);
else
simulate_dragged_click_point_to_point(locationInWindow1.x, locationInWindow1.y, locationInWindow2.x, locationInWindow2.y);
}
function _simulate_event(event)
{
var type = event["event"]["type"],
keyView = event["keyView"],
valueView = event["valueView"],
characters = event["event"]["characters"],
deltaX = event["event"]["deltaX"],
deltaY = event["event"]["deltaY"],
deltaZ = event["event"]["deltaZ"],
deltaZ = event["event"]["deltaZ"],
locationInWindow = CGPointMake(event["event"]["locationInWindow"]["x"], event["event"]["locationInWindow"]["y"]);
switch (type)
{
case CPScrollWheel:
if (keyView && valueView)
simulate_scroll_wheel_on_view(keyView, valueView, deltaX, deltaY)
break;
case CPLeftMouseDown:
if (keyView && valueView)
simulate_left_click_on_view(keyView, valueView);
else
simulate_left_click_on_point(locationInWindow.x, locationInWindow.y)
break;
case CPRightMouseDown:
if (keyView && valueView)
simulate_right_click_on_view(keyView, valueView);
else
simulate_right_click_on_point(locationInWindow.x, locationInWindow.y)
break;
case CPKeyDown:
simulate_keyboard_event(characters);
break;
}
}
var eventRecords,
recording = NO;
@implementation CPWindow (cucappRecord)
+ (CPArray)eventRecords
{
return eventRecords;
}
+ (void)start_record
{
eventRecords = [];
recording = YES;
}
+ (void)stop_record
{
recording = NO;
}
/*!
Dispatches events that are sent to it from CPApplication.
@param anEvent the event to be dispatched
*/
- (void)sendEvent:(CPEvent)anEvent
{
var type = [anEvent type],
sheet = [self attachedSheet],
recordingEvent = [[RecordingEvent alloc] initWithEvent:anEvent];
if (recordingEvent && type != CPFlagsChanged && type != CPMouseMoved)
[eventRecords addObject:recordingEvent];
// If a sheet is attached events get filtered here.
// It is not clear what events should be passed to the view, perhaps all?
// CPLeftMouseDown is needed for window moving and resizing to work.
// CPMouseMoved is needed for rollover effects on title bar buttons.
if (sheet)
{
switch (type)
{
case CPLeftMouseDown:
// This is needed when a doubleClick occurs when the sheet is closing or opening
if (!_parentWindow)
return;
[recordingEvent setView:_windowView];
[_windowView mouseDown:anEvent];
// -dw- if the window is clicked, the sheet should come to front, and become key,
// and the window should be immediately behind
[sheet makeKeyAndOrderFront:self];
return;
case CPMouseMoved:
// Allow these through to the parent
break;
default:
// Everything else is filtered
return;
}
}
var point = [anEvent locationInWindow];
switch (type)
{
case CPFlagsChanged:
return [[self firstResponder] flagsChanged:anEvent];
case CPKeyUp:
return [[self firstResponder] keyUp:anEvent];
case CPKeyDown:
if ([anEvent charactersIgnoringModifiers] === CPTabCharacter)
{
if ([anEvent modifierFlags] & CPShiftKeyMask)
[self selectPreviousKeyView:self];
else
[self selectNextKeyView:self];
// Make sure the browser doesn't try to do its own tab handling.
// This is important or the browser might blur the shared text field or token field input field,
// even that we just moved it to a new first responder.
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO]
return;
}
else if ([anEvent charactersIgnoringModifiers] === CPBackTabCharacter)
{
var didTabBack = [self selectPreviousKeyView:self];
if (didTabBack)
{
// Make sure the browser doesn't try to do its own tab handling.
// This is important or the browser might blur the shared text field or token field input field,
// even that we just moved it to a new first responder.
[[[anEvent window] platformWindow] _propagateCurrentDOMEvent:NO]
}
return didTabBack;
}
else if ([anEvent charactersIgnoringModifiers] == CPEscapeFunctionKey && [self _processKeyboardUIKey:anEvent])
{
return;
}
[[self firstResponder] keyDown:anEvent];
// Trigger the default button if needed
// FIXME: Is this only applicable in a sheet? See isse: #722.
if (![self disableKeyEquivalentForDefaultButton])
{
var defaultButton = [self defaultButton],
keyEquivalent = [defaultButton keyEquivalent],
modifierMask = [defaultButton keyEquivalentModifierMask];
if ([anEvent _triggersKeyEquivalent:keyEquivalent withModifierMask:modifierMask])
[[self defaultButton] performClick:self];
}
return;
case CPScrollWheel:
[recordingEvent setView:[_windowView hitTest:point]];
return [[_windowView hitTest:point] scrollWheel:anEvent];
case CPLeftMouseUp:
case CPRightMouseUp:
var hitTestedView = _leftMouseDownView,
selector = type == CPRightMouseUp ? @selector(rightMouseUp:) : @selector(mouseUp:);
if (!hitTestedView)
hitTestedView = [_windowView hitTest:point];
[recordingEvent setView:hitTestedView];
[hitTestedView performSelector:selector withObject:anEvent];
_leftMouseDownView = nil;
return;
case CPLeftMouseDown:
case CPRightMouseDown:
// This will return _windowView if it is within a resize region
_leftMouseDownView = [_windowView hitTest:point];
[recordingEvent setView:_leftMouseDownView];
if (_leftMouseDownView !== _firstResponder && [_leftMouseDownView acceptsFirstResponder])
[self makeFirstResponder:_leftMouseDownView];
[CPApp activateIgnoringOtherApps:YES];
var theWindow = [anEvent window],
selector = type == CPRightMouseDown ? @selector(rightMouseDown:) : @selector(mouseDown:);
if ([theWindow isKeyWindow] || ([theWindow becomesKeyOnlyIfNeeded] && ![_leftMouseDownView needsPanelToBecomeKey]))
return [_leftMouseDownView performSelector:selector withObject:anEvent];
else
{
// FIXME: delayed ordering?
[self makeKeyAndOrderFront:self];
if ([_leftMouseDownView acceptsFirstMouse:anEvent])
return [_leftMouseDownView performSelector:selector withObject:anEvent];
}
break;
case CPLeftMouseDragged:
case CPRightMouseDragged:
if (!_leftMouseDownView)
{
[recordingEvent setView:[_windowView hitTest:point]];
return [[_windowView hitTest:point] mouseDragged:anEvent];
}
[recordingEvent setView:_leftMouseDownView];
var selector;
if (type == CPRightMouseDragged)
{
selector = @selector(rightMouseDragged:)
if (![_leftMouseDownView respondsToSelector:selector])
selector = nil;
}
if (!selector)
selector = @selector(mouseDragged:)
return [_leftMouseDownView performSelector:selector withObject:anEvent];
case CPMouseMoved:
[_windowView setCursorForLocation:point resizing:NO];
// Ignore mouse moves for parents of sheets
if (!_acceptsMouseMovedEvents || sheet)
return;
if (!_mouseEnteredStack)
_mouseEnteredStack = [];
var hitTestView = [_windowView hitTest:point];
if ([_mouseEnteredStack count] && [_mouseEnteredStack lastObject] === hitTestView)
return [hitTestView mouseMoved:anEvent];
var view = hitTestView,
mouseEnteredStack = [];
while (view)
{
mouseEnteredStack.unshift(view);
view = [view superview];
}
var deviation = MIN(_mouseEnteredStack.length, mouseEnteredStack.length);
while (deviation--)
if (_mouseEnteredStack[deviation] === mouseEnteredStack[deviation])
break;
var index = deviation + 1,
count = _mouseEnteredStack.length;
if (index < count)
{
var event = [CPEvent mouseEventWithType:CPMouseExited location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0];
for (; index < count; ++index)
[_mouseEnteredStack[index] mouseExited:event];
}
index = deviation + 1;
count = mouseEnteredStack.length;
if (index < count)
{
var event = [CPEvent mouseEventWithType:CPMouseEntered location:point modifierFlags:[anEvent modifierFlags] timestamp:[anEvent timestamp] windowNumber:_windowNumber context:nil eventNumber:-1 clickCount:1 pressure:0];
for (; index < count; ++index)
[mouseEnteredStack[index] mouseEntered:event];
}
_mouseEnteredStack = mouseEnteredStack;
[hitTestView mouseMoved:anEvent];
}
}
@end
@implementation RecordingEvent : CPObject
{
CPEvent _event @accessors(property=event);
CPString _keyView @accessors(property=keyView);
CPString _valueView @accessors(property=valueView);
CGPoint _offsetView @accessors(property=offsetView);
int _offsetXPercentage @accessors(property=offsetXPercentage);
int _offsetYPercentage @accessors(property=offsetYPercentage);
}
- (id)initWithEvent:(CPEvent)anEvent
{
if (self = [super init])
{
_event = anEvent;
_offsetView = CGPointMakeZero();
_keyView = @"";
_valueView = @"";
}
return self;
}
- (void)setView:(CPView)aView
{
if ([aView respondsToSelector:@selector(cucappIdentifier)])
{
_keyView = @"cucappIdentifier";
_valueView = [aView cucappIdentifier];
}
else if ([aView respondsToSelector:@selector(identifier)])
{
_keyView = @"identifier";
_valueView = [aView identifier];
}
else if ([aView respondsToSelector:@selector(title)])
{
_keyView = @"title";
_valueView = [aView title];
}
else if ([aView respondsToSelector:@selector(placeholderString)])
{
_keyView = @"placeholderString";
_valueView = [aView placeholderString];
}
else if ([aView respondsToSelector:@selector(text)])
{
_keyView = @"text";
_valueView = [aView text];
}
else if ([aView respondsToSelector:@selector(tag)])
{
_keyView = @"tag";
_valueView = [aView tag];
}
else if ([aView respondsToSelector:@selector(label)])
{
_keyView = @"label";
_valueView = [aView label];
}
else if ([aView respondsToSelector:@selector(objectValue)])
{
_keyView = @"objectValue";
_valueView = [aView objectValue];
}
var globalPoint = [[aView superview] convertPointToBase:[aView frameOrigin]],
globalEventPoint = [_event locationInWindow];
_offsetView = CGPointMake(globalEventPoint.x - globalPoint.x, globalEventPoint.y - globalPoint.y);
_offsetXPercentage = _offsetView.x * 100 / [aView frameSize].width;
_offsetYPercentage = _offsetView.y * 100 / [aView frameSize].height;
}
- (CPString)objectToJSON
{
var json = {};
json["keyView"] = _keyView;
json["valueView"] = _valueView;
json["offsetXPercentage"] = _offsetXPercentage;
json["offsetYPercentage"] = _offsetYPercentage;
json["offsetView"] = {"x" : _offsetView.x, "y" : _offsetView.y};
var event = {};
event["type"] = [_event type];
event["deltaX"] = [_event deltaX];
event["deltaY"] = [_event deltaY];
event["deltaZ"] = [_event deltaZ];
event["characters"] = [_event characters];
event["charactersIgnoringModifiers"] = [_event charactersIgnoringModifiers];
event["clickCount"] = [_event clickCount];
event["modifierFlags"] = [_event modifierFlags];
event["locationInWindow"] = {"x" : [_event locationInWindow].x, "y" : [_event locationInWindow].y};
event["keyCode"] = [_event keyCode];
event["timestamp"] = [_event timestamp];
json["event"] = event;
return JSON.stringify(json, null, 4);
}
@end
@implementation CPApplication (cucappRecord)
/*!
Dispatches events to other objects.
@param anEvent the event to dispatch
*/
- (void)sendEvent:(CPEvent)anEvent
{
_currentEvent = anEvent;
CPEventModifierFlags = [anEvent modifierFlags];
var theWindow = [anEvent window];
// Check if this is a candidate for key equivalent...
if ([anEvent _couldBeKeyEquivalent] && [self _handleKeyEquivalent:anEvent])
// The key equivalent was handled.
return;
if ([anEvent type] == CPMouseMoved)
{
if (theWindow !== _lastMouseMoveWindow)
[_lastMouseMoveWindow _mouseExitedResizeRect];
_lastMouseMoveWindow = theWindow;
}
/*
Event listeners are processed from back to front so that newer event listeners normally take
precedence. If during the execution of a callback a new event listener is added, it should
be inserted after the current callback but before any higher priority callbacks. This makes
repeating event listeners (those that reinsert themselves) stable relative to each other.
*/
for (var i = _eventListeners.length - 1; i >= 0; i--)
{
var listener = _eventListeners[i];
if (listener._mask & (1 << [anEvent type]))
{
_eventListeners.splice(i, 1);
// In case the callback wants to add more listeners.
_eventListenerInsertionIndex = i;
listener._callback(anEvent);
var type = [anEvent type],
recordingEvent = [[RecordingEvent alloc] initWithEvent:anEvent];
if (recordingEvent && type != CPFlagsChanged && type != CPMouseMoved)
[eventRecords addObject:recordingEvent];
if (theWindow)
[recordingEvent setView:[theWindow._windowView hitTest:[anEvent locationInWindow]]];
if (listener._dequeue)
{
// Don't process the event normally and don't send it to any other listener.
_eventListenerInsertionIndex = _eventListeners.length;
return;
}
}
}
_eventListenerInsertionIndex = _eventListeners.length;
if (theWindow)
[theWindow sendEvent:anEvent];
}
@end
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Main cib file base name</key>
<string>MainMenu.cib</string>
<key>CPBundleName</key>
<string>CPViewControllerTest</string>
<key>CPBundleVersion</key>
<string>1.0</string>
<key>CPHumanReadableCopyright</key>
<string>Copyright © 2016, Your Company All rights reserved.</string>
</dict>
</plist>
@@ -0,0 +1,194 @@
/*
* Jakefile
* CPViewControllerTest
*
* Created by You on May 9, 2016.
* Copyright 2016, Your Company All rights reserved.
*/
var ENV = require("system").env,
FILE = require("file"),
JAKE = require("jake"),
task = JAKE.task,
FileList = JAKE.FileList,
app = require("cappuccino/jake").app,
configuration = ENV["CONFIG"] || ENV["CONFIGURATION"] || ENV["c"] || "Debug",
OS = require("os"),
projectName = "CPViewControllerTest";
app (projectName, function(task)
{
ENV["OBJJ_INCLUDE_PATHS"] = "Frameworks";
if (configuration === "Debug")
ENV["OBJJ_INCLUDE_PATHS"] = FILE.join(ENV["OBJJ_INCLUDE_PATHS"], configuration);
task.setBuildIntermediatesPath(FILE.join("Build", "CPViewControllerTest.build", configuration));
task.setBuildPath(FILE.join("Build", configuration));
task.setProductName("CPViewControllerTest");
task.setIdentifier("com.yourcompany.CPViewControllerTest");
task.setVersion("1.0");
task.setAuthor("Your Company");
task.setEmail("feedback @nospam@ yourcompany.com");
task.setSummary("CPViewControllerTest");
task.setSources(new FileList("**/*.j").exclude(FILE.join("Build", "**")).exclude(FILE.join("Frameworks", "Source", "**")));
task.setResources(new FileList("Resources/**"));
task.setIndexFilePath("index.html");
task.setInfoPlistPath("Info.plist");
if (configuration === "Debug")
task.setCompilerFlags("-DDEBUG -g");
else
task.setCompilerFlags("-O2");
});
task ("default", [projectName], function()
{
printResults(configuration);
});
task ("build", ["default"], function()
{
updateApplicationSize();
});
task ("debug", function()
{
configuration = ENV["CONFIGURATION"] = "Debug";
JAKE.subjake(["."], "build", ENV);
});
task ("release", function()
{
configuration = ENV["CONFIGURATION"] = "Release";
JAKE.subjake(["."], "build", ENV);
});
task ("run", ["debug"], function()
{
OS.system(["open", FILE.join("Build", "Debug", projectName, "index.html")]);
});
task ("run-release", ["release"], function()
{
OS.system(["open", FILE.join("Build", "Release", projectName, "index.html")]);
});
task ("deploy", ["release"], function()
{
FILE.mkdirs(FILE.join("Build", "Deployment", projectName));
OS.system(["press", "-f", FILE.join("Build", "Release", projectName), FILE.join("Build", "Deployment", projectName)]);
printResults("Deployment")
});
task ("desktop", ["release"], function()
{
FILE.mkdirs(FILE.join("Build", "Desktop", projectName));
require("cappuccino/nativehost").buildNativeHost(FILE.join("Build", "Release", projectName), FILE.join("Build", "Desktop", projectName, "CPViewControllerTest.app"));
printResults("Desktop")
});
task ("run-desktop", ["desktop"], function()
{
OS.system([FILE.join("Build", "Desktop", projectName, "CPViewControllerTest.app", "Contents", "MacOS", "NativeHost"), "-i"]);
});
function printResults(configuration)
{
print("----------------------------");
print(configuration+" app built at path: "+FILE.join("Build", configuration, projectName));
print("----------------------------");
}
function updateApplicationSize()
{
print("Calculating application file sizes...");
var contents = FILE.read(FILE.join("Build", configuration, projectName, "Info.plist"), { charset:"UTF-8" }),
format = CFPropertyList.sniffedFormatOfString(contents),
plist = CFPropertyList.propertyListFromString(contents),
totalBytes = {executable:0, data:0, mhtml:0};
// Get the size of all framework executables and sprite data
var frameworksDir = "Frameworks";
if (configuration === "Debug")
frameworksDir = FILE.join(frameworksDir, "Debug");
var frameworks = FILE.list(frameworksDir);
frameworks.forEach(function(framework)
{
if (framework !== "Source")
addBundleFileSizes(FILE.join(frameworksDir, framework), totalBytes);
});
// Read in the default theme name, and attempt to get its size
var themeName = plist.valueForKey("CPDefaultTheme") || "Aristo2",
themePath = nil;
if (themeName === "Aristo" || themeName === "Aristo2")
themePath = FILE.join(frameworksDir, "AppKit", "Resources", themeName + ".blend");
else
themePath = FILE.join("Frameworks", "Resources", themeName + ".blend");
if (FILE.isDirectory(themePath))
addBundleFileSizes(themePath, totalBytes);
// Add sizes for the app
addBundleFileSizes(FILE.join("Build", configuration, projectName), totalBytes);
print("Executables: " + totalBytes.executable + ", sprite data: " + totalBytes.data + ", total: " + (totalBytes.executable + totalBytes.data));
var dict = new CFMutableDictionary();
dict.setValueForKey("executable", totalBytes.executable);
dict.setValueForKey("data", totalBytes.data);
dict.setValueForKey("mhtml", totalBytes.mhtml);
plist.setValueForKey("CPApplicationSize", dict);
FILE.write(FILE.join("Build", configuration, projectName, "Info.plist"), CFPropertyList.stringFromPropertyList(plist, format), { charset:"UTF-8" });
}
function addBundleFileSizes(bundlePath, totalBytes)
{
var bundleName = FILE.basename(bundlePath),
environment = bundleName === "Foundation" ? "Objj" : "Browser",
bundlePath = FILE.join(bundlePath, environment + ".environment");
if (FILE.isDirectory(bundlePath))
{
var filename = bundleName + ".sj",
filePath = new FILE.Path(FILE.join(bundlePath, filename));
if (filePath.exists())
totalBytes.executable += filePath.size();
filePath = new FILE.Path(FILE.join(bundlePath, "dataURLs.txt"));
if (filePath.exists())
totalBytes.data += filePath.size();
filePath = new FILE.Path(FILE.join(bundlePath, "MHTMLData.txt"));
if (filePath.exists())
totalBytes.mhtml += filePath.size();
filePath = new FILE.Path(FILE.join(bundlePath, "MHTMLPaths.txt"));
if (filePath.exists())
totalBytes.mhtml += filePath.size();
}
}
task ("cucumber-test", function()
{
var SYSTEM = require("system");
OS.system("ln -s " + SYSTEM.prefix + "/packages/cucapp/Cucapp Cucapp")
var code = OS.system("cucumber");
OS.system("rm -f Cucapp; rm -f cucumber.html")
OS.exit(code);
});
File diff suppressed because one or more lines are too long
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment version="1050" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="450" id="451"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Window" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="371">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="335" y="390" width="480" height="360"/>
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="878"/>
<view key="contentView" id="372">
<rect key="frame" x="0.0" y="0.0" width="480" height="360"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button identifier="load" verticalHuggingPriority="750" id="B4t-mW-jyS">
<rect key="frame" x="20" y="312" width="70" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<buttonCell key="cell" type="push" title="Load" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="L4T-tk-zIe">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="load:" target="450" id="B2z-4h-JJK"/>
</connections>
</button>
<textField identifier="result" horizontalHuggingPriority="251" verticalHuggingPriority="750" id="EwI-DV-COg">
<rect key="frame" x="99" y="319" width="163" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="isViewLoaded=" id="w5e-oN-M00">
<font key="font" metaFont="system" size="17"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<binding destination="EPv-VX-kzA" name="displayPatternValue1" keyPath="selection.isViewLoaded" id="BIN-61-0Im">
<dictionary key="options">
<string key="NSDisplayPattern">isViewLoaded=%{value1}@</string>
<string key="NSNoSelectionPlaceholder">NO</string>
</dictionary>
</binding>
</connections>
</textField>
</subviews>
</view>
</window>
<customObject id="450" customClass="AppController">
<connections>
<outlet property="theWindow" destination="371" id="459"/>
<outlet property="viewController" destination="rLc-A4-CBZ" id="H6z-uM-FBg"/>
</connections>
</customObject>
<viewController nibName="ViewController" id="rLc-A4-CBZ"/>
<objectController id="EPv-VX-kzA">
<connections>
<outlet property="content" destination="rLc-A4-CBZ" id="Xfw-zG-Ryt"/>
</connections>
</objectController>
</objects>
</document>
@@ -0,0 +1 @@
280NPLIST;1.0;D;K;4;$topD;K;18;CPCibObjectDataKeyD;K;6;CP$UIDd;1;2E;E;K;8;$objectsA;S;5;$nullD;K;10;$classnameS;16;_CPCibObjectDataK;8;$classesA;S;16;_CPCibObjectDataS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;1E;K;28;_CPCibObjectDataNamesKeysKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataNamesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataClassesKeysKeyD;K;6;CP$UIDd;1;0E;K;32;_CPCibObjectDataClassesValuesKeyD;K;6;CP$UIDd;1;0E;K;30;_CPCibObjectDataConnectionsKeyD;K;6;CP$UIDd;1;4E;K;28;_CPCibObjectDataFrameworkKeyD;K;6;CP$UIDd;1;0E;K;26;_CPCibObjectDataNextOidKeyD;K;6;CP$UIDd;1;5E;K;30;_CPCibObjectDataObjectsKeysKeyD;K;6;CP$UIDd;1;6E;K;32;_CPCibObjectDataObjectsValuesKeyD;K;6;CP$UIDd;1;7E;K;26;_CPCibObjectDataOidKeysKeyD;K;6;CP$UIDd;1;8E;K;28;_CPCibObjectDataOidValuesKeyD;K;6;CP$UIDd;1;9E;K;28;_CPCibObjectDataFileOwnerKeyD;K;6;CP$UIDd;2;11E;K;33;_CPCibObjectDataVisibleWindowsKeyD;K;6;CP$UIDd;2;13E;E;D;K;10;$classnameS;7;CPArrayK;8;$classesA;S;7;CPArrayS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;15E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;16E;D;K;6;CP$UIDd;2;18E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;D;K;6;CP$UIDd;2;11E;D;K;6;CP$UIDd;2;11E;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;D;K;10;$classnameS;18;_CPCibCustomObjectK;8;$classesA;S;18;_CPCibCustomObjectS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;19E;E;D;K;10;$classnameS;5;CPSetK;8;$classesA;S;5;CPSetS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;12E;K;15;CPSetObjectsKeyD;K;6;CP$UIDd;2;20E;E;D;K;10;$classnameS;20;CPCibOutletConnectorK;8;$classesA;S;20;CPCibOutletConnectorS;14;CPCibConnectorS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;14E;K;24;_CPCibConnectorSourceKeyD;K;6;CP$UIDd;2;11E;K;29;_CPCibConnectorDestinationKeyD;K;6;CP$UIDd;2;18E;K;23;_CPCibConnectorLabelKeyD;K;6;CP$UIDd;2;21E;E;D;K;6;$classD;K;6;CP$UIDd;2;10E;K;27;_CPCibCustomObjectClassNameD;K;6;CP$UIDd;2;22E;E;D;K;10;$classnameS;18;_CPCibClassSwapperK;8;$classesA;S;18;_CPCibClassSwapperS;8;CPObjectE;E;D;K;6;$classD;K;6;CP$UIDd;2;17E;K;18;CPResponderMenuKeyD;K;6;CP$UIDd;1;0E;K;12;CPViewTagKeyD;K;6;CP$UIDd;2;23E;K;14;CPViewFrameKeyD;K;6;CP$UIDd;2;24E;K;15;CPViewBoundsKeyD;K;6;CP$UIDd;2;24E;K;22;CPViewAutoresizingMaskD;K;6;CP$UIDd;2;25E;K;19;CPViewThemeClassKeyD;K;6;CP$UIDd;1;0E;K;19;CPViewThemeStateKeyD;K;6;CP$UIDd;2;26E;K;14;CPViewScaleKeyD;K;6;CP$UIDd;2;27E;K;18;CPViewSizeScaleKeyD;K;6;CP$UIDd;2;27E;K;17;CPViewIsScaledKeyD;K;6;CP$UIDd;2;28E;K;19;CPViewAppearanceKeyD;K;6;CP$UIDd;1;0E;K;22;CPViewTrackingAreasKeyD;K;6;CP$UIDd;2;29E;K;30;_CPCibClassSwapperClassNameKeyD;K;6;CP$UIDd;2;30E;K;38;_CPCibClassSwapperOriginalClassNameKeyD;K;6;CP$UIDd;2;31E;E;S;16;CPViewControllerD;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;4;viewS;13;CPApplicationd;1;0S;20;{{0, 0}, {480, 272}}d;2;12S;6;normalS;6;{1, 1}F;D;K;6;$classD;K;6;CP$UIDd;1;3E;K;10;CP.objectsA;E;E;S;6;NSViewS;6;CPViewE;K;9;$archiverS;15;CPKeyedArchiverK;8;$versionS;6;100000E;
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="10117" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="10117"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSViewController">
<connections>
<outlet property="view" destination="c22-O7-iKe" id="jmy-2J-skS"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView id="c22-O7-iKe">
<rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
</customView>
</objects>
</document>
@@ -0,0 +1,173 @@
# Given the application is lauched
Given /^the application is launched$/ do
launched = app.gui.command "launched"
if !launched
raise "The application was not launched"
end
end
# Given I wait for n seconds
Given /^I wait for (\d+) seconds?$/ do |n|
sleep(eval("#{n.to_i}"))
end
# When I close the popover
When /^I close the popover$/ do
step "I hit the key escape"
end
# When I hit the key c
When /^I hit the key (.*)$/ do |key|
step "I hit the mask none and the key #{key}"
end
# When I hit the mask shif and the key c
When /^I hit the mask (.*) and the key (.*)$/ do |mask, key|
simulate_keyboard_event(key, mask)
end
# When the keys cucapp
When /^I hit the keys (.*)$/ do |keys|
step "I hit the mask none and keys #{keys}"
end
# When I hit the mask shif and the keys cucapp
When /^I hit the mask (.*) and keys (.*)$/ do |mask, keys|
simulate_keyboard_event(keys, mask)
end
# When I select all
When /^I select all$/ do
app.gui.simulate_keyboard_event "a", [$CPCommandKeyMask]
end
# When I save the document
When /^I save the document$/ do
app.gui.simulate_keyboard_event "s", [$CPCommandKeyMask]
end
# When I (click|right click|double click) on the field with the value cucapp
When /^I (click|right click|double click) on the (\w*\-*\w*) with the value (.*)$/ do |click_type, element, value|
step "I #{click_type} on the #{element} with the property object-value set to #{value}"
end
# When I click on the field with the property cucapp-identifier set to cucapp-identifier-button-add
When /^I (click|right click|double click) on the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |click_type, element, property, property_value|
step "I #{click_type} with the key mask none on the #{element} with the property #{property} set to #{property_value}"
end
# When I click with the mask shift on the field with the property cucapp-identifier set to cucapp-identifier-button-add
When /^I (click|right click|double click) with the key mask (.*) on the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |click_type, mask, element, property, property_value|
type = $mouse_left_click
if click_type == "right click"
type = $mouse_right_click
end
if click_type == "double click"
type = $mouse_double_click
end
simulate_click(type, element, property, property_value, mask)
end
# When I do a drag and drop from the field with the value cucapp to the field with the value cappuccino
When /^I do a drag and drop from the (\w*\-*\w*) with the value (.*) to the (\w*\-*\w*) with the value (.*)$/ do |element, value, second_element, second_value|
step "I do a drag and drop from the #{element} with the property object-value set to #{value} to the #{second_element} with the property object-value set to #{second_value}"
end
# When I do a drag and drop from the field with the property title set to cucapp to the field with the property title set to cappuccino
When /^I do a drag and drop from the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) to the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |element, property, property_value, second_element, second_property, second_property_value|
step "I do a drag and drop with the key mask none from the #{element} with the property #{property} set to #{property_value} to the #{second_element} with the property #{second_property} set to #{second_property_value}"
end
# When I do a drag and drop with the key mask shift from the field with the property title set to cucapp to the field with the property title set to cappuccino
When /^I do a drag and drop with the key mask (.*) from the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) to the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |mask, element, property, property_value, second_element, second_property, second_property_value|
simulate_drag_and_drop(element, property, property_value, second_element, second_property, second_property_value, mask)
end
# When I vertically|horizontally scroll on the field with the value cappuccino
When /^I (vertically|horizontally) scroll on the (\w*\-*\w*) with the value (.*)$/ do |direction, element, value|
step "When I #{direction} scroll 10 times on the #{element} with the value #{value}"
end
# When I vertically|horizontally scroll 10 times on the field with the value cappuccino
When /^I (vertically|horizontally) scroll ([0-9]*) times on the (\w*\-*\w*) with the value (.*)$/ do |direction, times, element, value|
step "I #{direction} scroll #{times} times on the #{element} with the property object-value set to #{value}"
end
# When I vertically|horizontally scroll 10 times on the field with the property title set to cappuccino
When /^I (vertically|horizontally) scroll ([0-9]*) times on the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |direction, times, element, property, property_value|
step "I #{direction} scroll #{times} times with the key mask none on the #{element} with the property #{property} set to #{property_value}"
end
# When I vertically|horizontally scroll 10 times on the field with the property title set to cappuccino
When /^I (vertically|horizontally) scroll ([0-9]*) times with the key mask (.*) on the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*)$/ do |direction, times, mask, element, property, property_value|
vertically = false
horizontally = false
if direction == "vertically"
vertically = true
end
if direction == "horizontally"
horizontally = true
end
simulate_scroll(element, property, property_value, times, mask, horizontally, vertically)
end
# When I select the item name of the pop-up-button with the property cucapp-identifier set to cucappIdentifierPopUpButton
When /^I select the item (.*) of the pop-up-button with the property (\w*\-*\w*) set to (.*)$/ do |item_name, property, property_value|
select_pop_up_button_item(item_name, property, property_value)
end
# Then the field with the property title set to name should be focused
Then /^the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) should be focused$/ do |element, property, property_value|
app.gui.is_control_focused(create_xpath(element, property, property_value))
end
# Then the field should not have a value
Then /^the (\w*\-*\w*) should not have a value$/ do |element|
step "the #{element} with the property object-value set to #{value} should have the value #{value}"
end
# Then the field with the property cucapp-identifier set to cucapp-identifier-textfield-description should not have a value
Then /^the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) should not have a value$/ do |element, property, property_value|
check_value_control(element, property, property_value, nil)
end
# Then the field should have the value cucapp
Then /^the (\w*\-*\w*) should have the value (.*)$/ do |element, value|
step "the #{element} with the property object-value set to #{value} should have the value #{value}"
end
# Then the field with the property cucapp-identifier set to cucapp-identifier-textfield-description should have the value cucapp
Then /^the (\w*\-*\w*) with the property (\w*\-*\w*) set to (.*) should have the value (.*)$/ do |element, property, property_value, value|
check_value_control(element, property, property_value, value)
end
@@ -0,0 +1,19 @@
@import <Foundation/Foundation.j>
@implementation Cucumber (CuCapp)
- (CPString)valueIsEqual:(CPArray)params
{
var obj = cucumber_objects[params[0]],
value = params[1];
if (!obj)
return '{"result" : "__CUKE_ERROR__"}';
if ([obj respondsToSelector:@selector(stringValue)] && value === [obj stringValue])
return '{"result" : "OK"}';
return '{"result" : "__CUKE_ERROR__"}';
}
@end
@@ -0,0 +1,62 @@
$cappuccino_control_mappings = {
"image-view-text" => "_CPImageAndTextView",
"menu-item" => "_CPMenuItemView",
"tool-bar-item" => "_CPToolbarItemView",
"box" => "CPBox",
"button" => "CPButton",
"button-bar" => "CPButtonBar",
"collection-view" => "CPCollectionView",
"combo-box" => "CPComboBox",
"control" => "CPControl",
"check-box" => "CPCheckBox",
"date-picker" => "CPDatePicker",
"image-view" => "CPImageView",
"level-indicator" => "CPLevelIndicator",
"outline-view" => "CPOutlineView",
"pop-up-button" => "CPPopUpButton",
"predicate-editor" => "CPPredicateEditor",
"radio-button" => "CPRadio",
"rule-editor" => "CPRuleEditor",
"scroller" => "CPScroller",
"scroll-view" => "CPScrollView",
"search-field" => "CPSearchField",
"secure-field" => "CPSecureTextField",
"segemented-control" => "CPSegmentedControl",
"slider" => "CPSlider",
"stepper" => "CPStepper",
"tab-view" => "CPTabView",
"table" => "CPTableView",
"field" => "CPTextField",
"token-field" => "CPTokenField",
"view" => "CPView",
"none" => nil
}
$property_mappings = {
"cucapp-identifier" => "cucappIdentifier",
"id" => "id",
"identifier" => "identifier",
"label" => "label",
"object-value" => "objectValue",
"placeholder" => "placeholderString",
"tag" => "tag",
"title" => "title",
"text" => "text",
"none" => nil
}
$key_mappings = {
"command" => $CPCommandKeyMask,
"shift" => $CPShiftKeyMask,
"option" => $CPAlternateKeyMask,
"control" => $CPControlKeyMask,
"delete" => $CPDeleteCharacter,
"escape" => $CPEscapeFunctionKey,
"enter" => $CPNewlineCharacter,
"left-arrow" => $CPLeftArrowFunctionKey,
"right-arrow" => $CPRightArrowFunctionKey,
"top-arrow" => $CPTopArrowFunctionKey,
"down-arrow" => $CPDownArrowFunctionKey,
"tab" => $CPTabCharacter,
"none" => nil
}
@@ -0,0 +1,14 @@
module Encumber
class GUI
def value_is_equal(xpath, value)
if !value
value = ""
end
result = command 'valueIsEqual', id_for_element(xpath), value
raise "Value #{value} not found" if result["result"] != "OK"
end
end
end
@@ -0,0 +1,20 @@
$: << File.join(File.dirname(__FILE__), '..', '..', 'Cucapp')
require 'cucapp.rb'
require 'logger'
module AppHelper
def app
@app ||= Cucapp.new
end
def log
@log ||= ENV['log']
end
end
World(
AppHelper
)
@@ -0,0 +1,7 @@
Before do
app.reset()
end
After do
app.quit()
end
@@ -0,0 +1,92 @@
def check_value_control(element, property, property_value, value)
xpath = create_xpath(element, property, property_value)
app.gui.wait_for xpath
app.gui.value_is_equal xpath, value
end
def simulate_keyboard_event(keys, mask)
app.gui.simulate_keyboard_events cappuccino_key(keys), [cappuccino_key(mask)]
end
def simulate_click(type, element, property, property_value, mask)
xpath = create_xpath(element, property, property_value)
app.gui.wait_for xpath
if type == $mouse_double_click
app.gui.simulate_double_click xpath, [cappuccino_key(mask)]
elsif type == $mouse_right_click
app.gui.simulate_right_click xpath, [cappuccino_key(mask)]
else
app.gui.simulate_left_click xpath, [cappuccino_key(mask)]
end
end
def simulate_drag_and_drop(element, property, property_value, second_element, second_property, second_property_value, mask)
xpath1 = create_xpath(element, property, property_value)
xpath2 = create_xpath(second_element, second_property, second_property_value)
app.gui.simulate_dragged_click_view_to_view xpath1, xpath2, [cappuccino_key(mask)]
end
def simulate_scroll(element, property, property_value, times, mask, horizontal, vertical)
xpath = create_xpath(element, property, property_value)
delta_x = 0
delta_y = 0
if horizontal
delta_x = 1
end
if vertical
delta_y = 1
end
for i in 0..times.to_i
app.gui.simulate_scroll_wheel xpath, delta_x, delta_y, [cappuccino_key(mask)]
end
end
def select_pop_up_button_item(item_name, property, property_value)
simulate_click($mouse_left_click, "pop-up-button", property, property_value, [])
pop_up_button_xpath = create_xpath("pop-up-button", property, property_value)
pop_up_button_item_xpath = create_xpath("image-view-text", "text", item_name)
while !app.gui.wait_for_element(pop_up_button_item_xpath, 0.05) && app.gui.pop_up_button_can_scroll_up(pop_up_button_xpath)
simulate_keyboard_event("up-arrow", [])
end
while !app.gui.wait_for_element(pop_up_button_item_xpath, 0.05) && app.gui.pop_up_button_can_scroll_down(pop_up_button_xpath)
simulate_keyboard_event("down-arrow", [])
end
if not app.gui.wait_for(pop_up_button_item_xpath)
raise "Menu item #{item_name} not found !"
end
simulate_click($mouse_left_click, "image-view-text", "text", item_name, [])
end
def cappuccino_key(key)
if $key_mappings.has_key?(key)
key = $key_mappings[key]
end
return key
end
def create_xpath(element, property, property_value)
if !$cappuccino_control_mappings.has_key?(element)
raise "Element #{element} not found in the hash cappuccino_control_mappings. You should complete the hash $cappuccino_control_mappings in env.rb"
end
if !$property_mappings.has_key?(property)
raise "Property #{property} not found in the hash $property_mappings. You should complete the hash $property_mappings in env.rb"
end
return "//" + $cappuccino_control_mappings[element] + "["+ $property_mappings[property] +"='#{property_value}']"
end
@@ -0,0 +1,8 @@
Feature: Test the CPViewController asynchronous loading
This test is used to make sure that the isViewLoaded property is true when the loading has ended.
Scenario: Check if the application is launched
Given the application is launched
When I click on the button with the property identifier set to load
Given I wait for 1 second
Then the field with the property identifier set to result should have the value isViewLoaded=true
@@ -0,0 +1,204 @@
<!DOCTYPE html>
<html lang="en">
<!--
index-debug.html
CPViewControllerTest
Created by You on May 9, 2016.
Copyright 2016, Your Company All rights reserved.
-->
<head>
<meta charset="utf-8">
<!--[if lte IE 8]>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1">
<![endif]-->
<!--[if gte IE 9]>
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
<![endif]-->
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon" href="Resources/icon.png">
<link rel="apple-touch-startup-image" href="Resources/default.png">
<title>CPViewControllerTest</title>
<!-- Custom javascript goes here -->
<!-- End custom javascript -->
<script type="text/javascript">
OBJJ_MAIN_FILE = "main.j";
OBJJ_INCLUDE_PATHS = ["Frameworks/Debug", "Frameworks"];
// The below will tell the compiler to generate debug symbols, type signatures and not inline objj_msgSend functions.
// This will affect only Objective-J code that is compiled when loading the application. It will not affect precompiled
// code like the Cappuccino frameworks.
//
// Debug symbols will give each Objective-J method a Javascript function name. Without a name it will be very hard to find
// the methods in the debugger.
// Type Signatures will give type information to the Objective-J runtime for instance variables and methods.
// Inline objj_msgSend will give a speed increase but decorators will not work. Check below on debug options for
// more information on decorators.
//
// Uncomment or comment on the line below to change the flags
OBJJ_COMPILER_FLAGS = ["IncludeDebugSymbols", "IncludeTypeSignatures"/*, "InlineMsgSend"*/];
var progressBar = null;
OBJJ_PROGRESS_CALLBACK = function(percent, appSize, path)
{
percent = percent * 100;
if (!progressBar)
progressBar = document.getElementById("progress-bar");
if (progressBar)
progressBar.style.width = Math.min(percent, 100) + "%";
}
var loadingHTML =
'<div id="loading">' +
' <div id="loading-text">Loading...</div>' +
' <div id="progress-indicator">' +
' <span id="progress-bar" style="width:0%"></span>' +
' </div>' +
'</div>';
</script>
<script type="text/javascript" src="Frameworks/Debug/Objective-J/Objective-J.js" charset="UTF-8"></script>
<script type="text/javascript">
objj_msgSend_reset();
// DEBUG OPTIONS:
// Decorators will only work when the code is compiled without the compiler option 'InlineMsgSend'. Check above.
// Uncomment to enable printing of backtraces on exceptions:
//objj_msgSend_decorate(objj_backtrace_decorator);
// Uncomment to supress exceptions that take place inside a message
//objj_msgSend_decorate(objj_supress_exceptions_decorator)
// Uncomment to enable runtime type checking:
//objj_msgSend_decorate(objj_typecheck_decorator);
// Uncomment (along with both above) to print backtraces on type check errors:
//objj_typecheck_prints_backtrace = true;
// Uncomment to disable the default logger (CPLogConsole if window.console exists, CPLogPopup otherwise):
//CPLogUnregister(CPLogDefault);
// Uncomment to enable a specific logger:
//CPLogRegister(CPLogConsole);
//CPLogRegister(CPLogPopup);
// Tag view DOM elements with a "data-cappuccino-view" attribute that contains
// the class name of the view that created them. Comment this or set to false to disable.
appkit_tag_dom_elements = true;
</script>
<style type="text/css">
html, body, h1, p {
margin: 0;
padding: 0;
}
/* We need a body wrapper because Cappuccino is unhappy if we change the body element */
#cappuccino-body {
/* Position it absolutely so it will fill the height without content */
position: absolute;
top: 0;
bottom: 0;
width: 100%;
/* Put it at the bottom of the stack so it doesn't interfere with UI */
z-index: 0;
}
#cappuccino-body .container {
display: table;
margin: 0 auto;
height: 100%;
}
#cappuccino-body .content {
display: table-cell;
height: 100%;
vertical-align: top;
}
#loading {
position: relative;
top: 35%;
}
#loading-text {
height: 1.5em;
color: #555;
font: normal bold 36px/36px Arial, sans-serif;
}
#progress-indicator {
padding: 0px;
height: 16px;
border: 5px solid #555;
border-radius: 18px;
background-color: white;
}
#progress-bar {
position: relative;
top: -1px;
left: -1px;
display: block;
height: 18px;
/* Compensate for moving the bar left 1px to overlap the indicator border */
border-right: 1px solid #555;
background-color: #555;
}
#noscript {
position: relative;
top: 35%;
padding: 1em 1.5em;
border: 5px solid #555;
border-radius: 16px;
background-color: white;
color: #555;
text-align: center;
font: bold 24px Arial, sans-serif;
}
#noscript a {
color: #98c0ff;
text-decoration: none;
}
</style>
</head>
<body>
<div id="cappuccino-body">
<div class="container">
<div class="content">
<script type="text/javascript">
document.write(loadingHTML);
</script>
</div>
</div>
<noscript style="position:absolute; top:0; left:0; width:100%; height:100%">
<div class="container">
<div class="content">
<div id="noscript">
<p style="font-size:120%; margin-bottom:.75em">JavaScript is required for this site.</p>
<p><a href="http://www.enable-javascript.com" target="_blank">Enable JavaScript</a></p>
</div>
</div>
</div>
</noscript>
</div>
</body>
</html>
@@ -0,0 +1,166 @@
<!DOCTYPE html>
<html lang="en">
<!--
index.html
CPViewControllerTest
Created by You on May 9, 2016.
Copyright 2016, Your Company All rights reserved.
-->
<head>
<meta charset="utf-8">
<!--[if lte IE 8]>
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7, chrome=1">
<![endif]-->
<!--[if gte IE 9]>
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
<![endif]-->
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<link rel="apple-touch-icon" href="Resources/icon.png">
<link rel="apple-touch-startup-image" href="Resources/default.png">
<title>CPViewControllerTest</title>
<!-- Custom javascript goes here -->
<!-- End custom javascript -->
<script type="text/javascript">
OBJJ_MAIN_FILE = "main.j";
// The below will tell the compiler to not generate debug symbols but will generate type signatures and inline objj_msgSend functions.
// This will affect only Objective-J code that is compiled when loading the application. It will not affect precompiled
// code like the Cappuccino frameworks.
// Uncomment or comment on the line below to change the flags
OBJJ_COMPILER_FLAGS = [/*"IncludeDebugSymbols"*/, "IncludeTypeSignatures", "InlineMsgSend"];
var progressBar = null;
OBJJ_PROGRESS_CALLBACK = function(percent, appSize, path)
{
percent = percent * 100;
if (!progressBar)
progressBar = document.getElementById("progress-bar");
if (progressBar)
progressBar.style.width = Math.min(percent, 100) + "%";
}
var loadingHTML =
'<div id="loading">' +
' <div id="loading-text">Loading...</div>' +
' <div id="progress-indicator">' +
' <span id="progress-bar" style="width:0%"></span>' +
' </div>' +
'</div>';
</script>
<script type="text/javascript" src="Frameworks/Objective-J/Objective-J.js" charset="UTF-8"></script>
<style type="text/css">
html, body, h1, p {
margin: 0;
padding: 0;
}
/* We need a body wrapper because Cappuccino is unhappy if we change the body element */
#cappuccino-body {
/* Position it absolutely so it will fill the height without content */
position: absolute;
top: 0;
bottom: 0;
width: 100%;
/* Put it at the bottom of the stack so it doesn't interfere with UI */
z-index: 0;
}
#cappuccino-body .container {
display: table;
margin: 0 auto;
height: 100%;
}
#cappuccino-body .content {
display: table-cell;
height: 100%;
vertical-align: top;
}
#loading {
position: relative;
top: 35%;
}
#loading-text {
height: 1.5em;
color: #555;
font: normal bold 36px/36px Arial, sans-serif;
}
#progress-indicator {
padding: 0px;
height: 16px;
border: 5px solid #555;
border-radius: 18px;
background-color: white;
}
#progress-bar {
position: relative;
top: -1px;
left: -1px;
display: block;
height: 18px;
/* Compensate for moving the bar left 1px to overlap the indicator border */
border-right: 1px solid #555;
background-color: #555;
}
#noscript {
position: relative;
top: 35%;
padding: 1em 1.5em;
border: 5px solid #555;
border-radius: 16px;
background-color: white;
color: #555;
text-align: center;
font: bold 24px Arial, sans-serif;
}
#noscript a {
color: #98c0ff;
text-decoration: none;
}
</style>
</head>
<body>
<div id="cappuccino-body">
<div class="container">
<div class="content">
<script type="text/javascript">
document.write(loadingHTML);
</script>
</div>
</div>
<noscript style="position:absolute; top:0; left:0; width:100%; height:100%">
<div class="container">
<div class="content">
<div id="noscript">
<p style="font-size:120%; margin-bottom:.75em">JavaScript is required for this site.</p>
<p><a href="http://www.enable-javascript.com" target="_blank">Enable JavaScript</a></p>
</div>
</div>
</div>
</noscript>
</div>
</body>
</html>
@@ -0,0 +1,18 @@
/*
* AppController.j
* CPViewControllerTest
*
* Created by You on May 9, 2016.
* Copyright 2016, Your Company All rights reserved.
*/
@import <Foundation/Foundation.j>
@import <AppKit/AppKit.j>
@import "AppController.j"
function main(args, namedArgs)
{
CPApplicationMain(args, namedArgs);
}