mirror of
https://github.com/cappuccino/cappuccino.git
synced 2026-11-03 21:58:59 +00:00
Ensure decoded views are correct by verifying they retain their connections and respond as before archiving. A decoded table no longer raises, and decoded button bars, tab views, text fields and outlines retain their observers, selection, delegates and data source methods. * CPButtonBar: Observe the hidden state of decoded buttons. * CPOutlineView: Decode the data source and delegate through their setters. * CPTableView: Set the data source and delegate after _init during decoding. * CPTabView: Select the archived tab view item during decoding, before the delegate is set. * CPTextField: Archive the delegate. * CPViewDecodedBehaviorTest: Verifies connections, the view hierarchy, and the behaviour of objects each class creates or observes after decoding.
476 lines
14 KiB
Plaintext
476 lines
14 KiB
Plaintext
/*
|
|
* CPViewDecodedBehaviorTest.j
|
|
* AppKit
|
|
*
|
|
* Created by David Richardson on 31/10/2026.
|
|
* Copyright David Richardson 2026.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
@import <AppKit/AppKit.j>
|
|
|
|
/*
|
|
DropInArchiveTest.j checks that a decoded view has the right content. These tests
|
|
check that it still works: that its connections were restored, and that the
|
|
objects a class creates or observes for itself were set up again.
|
|
|
|
A target or delegate is encoded conditionally, so each test archives a container
|
|
that holds the target too. Bindings are not here. They are stored in cibs as
|
|
connectors, and not in the views.
|
|
*/
|
|
|
|
@implementation _CPDecodedTarget : CPView
|
|
{
|
|
int actionCount @accessors;
|
|
id lastSender @accessors;
|
|
}
|
|
|
|
- (void)performAction:(id)aSender
|
|
{
|
|
actionCount++;
|
|
lastSender = aSender;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _CPDecodedTableDataSource : CPObject
|
|
{
|
|
}
|
|
|
|
- (id)initWithCoder:(CPCoder)aCoder
|
|
{
|
|
return [self init];
|
|
}
|
|
|
|
- (void)encodeWithCoder:(CPCoder)aCoder
|
|
{
|
|
}
|
|
|
|
- (int)numberOfRowsInTableView:(CPTableView)aTableView
|
|
{
|
|
return 3;
|
|
}
|
|
|
|
- (id)tableView:(CPTableView)aTableView objectValueForTableColumn:(CPTableColumn)aColumn row:(int)aRow
|
|
{
|
|
return @"Row";
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation _CPDecodedOutlineDataSource : _CPDecodedTableDataSource
|
|
{
|
|
}
|
|
|
|
- (id)outlineView:(CPOutlineView)anOutlineView child:(int)anIndex ofItem:(id)anItem
|
|
{
|
|
return anItem ? @"Child" : @"Parent";
|
|
}
|
|
|
|
- (BOOL)outlineView:(CPOutlineView)anOutlineView isItemExpandable:(id)anItem
|
|
{
|
|
return anItem === @"Parent";
|
|
}
|
|
|
|
- (int)outlineView:(CPOutlineView)anOutlineView numberOfChildrenOfItem:(id)anItem
|
|
{
|
|
return (!anItem || anItem === @"Parent") ? 1 : 0;
|
|
}
|
|
|
|
- (id)outlineView:(CPOutlineView)anOutlineView objectValueForTableColumn:(CPTableColumn)aColumn byItem:(id)anItem
|
|
{
|
|
return anItem;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation CPViewDecodedBehaviorTest : OJTestCase
|
|
{
|
|
CPView container;
|
|
_CPDecodedTarget target;
|
|
}
|
|
|
|
- (void)setUp
|
|
{
|
|
// This will init the global var CPApp which are used internally in the AppKit
|
|
[[CPApplication alloc] init];
|
|
|
|
container = [[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 300.0)];
|
|
target = [[_CPDecodedTarget alloc] initWithFrame:CGRectMakeZero()];
|
|
|
|
[container addSubview:target];
|
|
}
|
|
|
|
- (id)archived:(id)anObject
|
|
{
|
|
return [CPKeyedUnarchiver unarchiveObjectWithData:[CPKeyedArchiver archivedDataWithRootObject:anObject]];
|
|
}
|
|
|
|
/*
|
|
Connects a control to the target, archives the container, and returns the decoded
|
|
control. The decoded target is its action's receiver.
|
|
*/
|
|
- (id)decodedConnectedControl:(CPControl)aControl
|
|
{
|
|
[aControl setTarget:target];
|
|
[aControl setAction:@selector(performAction:)];
|
|
[container addSubview:aControl];
|
|
|
|
return [[self archived:container] subviews][1];
|
|
}
|
|
|
|
// MARK: Connections
|
|
|
|
- (void)testAControlKeepsItsTargetAndAction
|
|
{
|
|
var decoded = [self decodedConnectedControl:[[CPButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 80.0, 24.0)]],
|
|
decodedTarget = [[decoded superview] subviews][0];
|
|
|
|
[self assert:decodedTarget equals:[decoded target]];
|
|
[self assert:@selector(performAction:) equals:[decoded action]];
|
|
}
|
|
|
|
- (void)testADecodedButtonSendsItsAction
|
|
{
|
|
var decoded = [self decodedConnectedControl:[[CPButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 80.0, 24.0)]];
|
|
|
|
[decoded performClick:nil];
|
|
|
|
[self assert:1 equals:[[decoded target] actionCount]];
|
|
[self assert:decoded equals:[[decoded target] lastSender]];
|
|
}
|
|
|
|
- (void)testADecodedSliderSendsItsAction
|
|
{
|
|
var decoded = [self decodedConnectedControl:[[CPSlider alloc] initWithFrame:CGRectMake(0.0, 0.0, 80.0, 24.0)]];
|
|
|
|
[decoded sendAction:[decoded action] to:[decoded target]];
|
|
|
|
[self assert:1 equals:[[decoded target] actionCount]];
|
|
}
|
|
|
|
- (void)testADecodedTextFieldKeepsItsDelegate
|
|
{
|
|
var field = [[CPTextField alloc] initWithFrame:CGRectMake(0.0, 0.0, 80.0, 24.0)];
|
|
|
|
[field setDelegate:target];
|
|
[container addSubview:field];
|
|
|
|
var decoded = [[self archived:container] subviews][1];
|
|
|
|
[self assert:[[decoded superview] subviews][0] equals:[decoded delegate]];
|
|
}
|
|
|
|
- (void)testADecodedTabViewKeepsItsDelegate
|
|
{
|
|
var tabView = [[CPTabView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
|
|
|
|
[tabView setDelegate:target];
|
|
[container addSubview:tabView];
|
|
|
|
var decoded = [[self archived:container] subviews][1];
|
|
|
|
[self assert:[[decoded superview] subviews][0] equals:[decoded delegate]];
|
|
}
|
|
|
|
- (void)testADecodedSplitViewKeepsItsDelegate
|
|
{
|
|
var splitView = [[CPSplitView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
|
|
|
|
[splitView setDelegate:target];
|
|
[container addSubview:splitView];
|
|
|
|
var decoded = [[self archived:container] subviews][1];
|
|
|
|
[self assert:[[decoded superview] subviews][0] equals:[decoded delegate]];
|
|
}
|
|
|
|
// MARK: The view hierarchy
|
|
|
|
- (void)testADecodedSubviewKeepsItsResponderChain
|
|
{
|
|
var child = [[CPView alloc] initWithFrame:CGRectMakeZero()];
|
|
|
|
[container addSubview:child];
|
|
|
|
var decoded = [self archived:container];
|
|
|
|
[self assert:decoded equals:[[decoded subviews][1] nextResponder]];
|
|
}
|
|
|
|
- (void)testADecodedViewAutoresizesItsSubviews
|
|
{
|
|
var child = [[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
|
|
|
|
[child setAutoresizingMask:CPViewWidthSizable];
|
|
[container addSubview:child];
|
|
|
|
var decoded = [self archived:container];
|
|
|
|
[decoded setFrameSize:CGSizeMake(400.0, 300.0)];
|
|
|
|
[self assert:200.0 equals:CGRectGetWidth([[decoded subviews][1] frame])];
|
|
}
|
|
|
|
// MARK: Objects a class creates or observes
|
|
|
|
- (void)testADecodedButtonBarFollowsAHiddenButton
|
|
{
|
|
var buttonBar = [[CPButtonBar alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 26.0)],
|
|
buttons = [[CPButtonBar plusButton], [CPButtonBar minusButton]];
|
|
|
|
[buttonBar setButtons:buttons];
|
|
[buttonBar layoutSubviews];
|
|
|
|
var decoded = [self archived:buttonBar],
|
|
decodedButtons = [decoded buttons];
|
|
|
|
[decoded layoutSubviews];
|
|
[_CPDisplayServer run];
|
|
|
|
var firstOrigin = CGRectGetMinX([decodedButtons[0] frame]);
|
|
|
|
[decodedButtons[0] setHidden:YES];
|
|
[_CPDisplayServer run];
|
|
|
|
[self assert:firstOrigin equals:CGRectGetMinX([decodedButtons[1] frame])];
|
|
}
|
|
|
|
- (void)testADecodedStepperStepsItsValue
|
|
{
|
|
var stepper = [[CPStepper alloc] initWithFrame:CGRectMake(0.0, 0.0, 30.0, 25.0)];
|
|
|
|
[stepper setMinValue:1.0];
|
|
[stepper setMaxValue:9.0];
|
|
[stepper setDoubleValue:4.0];
|
|
|
|
var decoded = [self decodedConnectedControl:stepper];
|
|
|
|
[decoded performClickUp:nil];
|
|
[self assert:5.0 equals:[decoded doubleValue]];
|
|
|
|
[decoded performClickDown:nil];
|
|
[self assert:4.0 equals:[decoded doubleValue]];
|
|
|
|
[self assert:2 equals:[[decoded target] actionCount]];
|
|
}
|
|
|
|
- (void)testADecodedSearchFieldCancelsItsText
|
|
{
|
|
var field = [[CPSearchField alloc] initWithFrame:CGRectMake(0.0, 0.0, 120.0, 30.0)];
|
|
|
|
[field setStringValue:@"Text"];
|
|
|
|
var decoded = [self archived:field];
|
|
|
|
[decoded cancelOperation:nil];
|
|
|
|
[self assert:@"" equals:[decoded stringValue]];
|
|
[self assertTrue:[[decoded cancelButton] isHidden]];
|
|
}
|
|
|
|
- (void)testADecodedPopUpButtonAcceptsNewItems
|
|
{
|
|
var button = [[CPPopUpButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 120.0, 24.0)];
|
|
|
|
[button addItemsWithTitles:[@"One", @"Two"]];
|
|
[button selectItemAtIndex:1];
|
|
|
|
var decoded = [self archived:button];
|
|
|
|
[self assert:1 equals:[decoded indexOfSelectedItem]];
|
|
[self assert:@"Two" equals:[decoded titleOfSelectedItem]];
|
|
|
|
[decoded addItemWithTitle:@"Three"];
|
|
[decoded selectItemAtIndex:2];
|
|
|
|
[self assert:3 equals:[decoded numberOfItems]];
|
|
[self assert:@"Three" equals:[decoded title]];
|
|
}
|
|
|
|
- (void)testADecodedSegmentedControlSelectsASegment
|
|
{
|
|
var control = [[CPSegmentedControl alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 24.0)];
|
|
|
|
[control setSegmentCount:2];
|
|
[control setLabel:@"One" forSegment:0];
|
|
[control setLabel:@"Two" forSegment:1];
|
|
[control setSelectedSegment:0];
|
|
|
|
var decoded = [self archived:control];
|
|
|
|
[self assert:0 equals:[decoded selectedSegment]];
|
|
[self assert:@"Two" equals:[decoded labelForSegment:1]];
|
|
|
|
[decoded setSelectedSegment:1];
|
|
|
|
[self assert:1 equals:[decoded selectedSegment]];
|
|
}
|
|
|
|
- (void)testADecodedTabViewSwitchesItsContent
|
|
{
|
|
var tabView = [[CPTabView alloc] initWithFrame:CGRectMake(0.0, 0.0, 200.0, 200.0)];
|
|
|
|
[@"one", @"two"].forEach(function(anIdentifier)
|
|
{
|
|
var item = [[CPTabViewItem alloc] initWithIdentifier:anIdentifier];
|
|
|
|
[item setLabel:anIdentifier];
|
|
[item setView:[[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)]];
|
|
[tabView addTabViewItem:item];
|
|
});
|
|
|
|
[tabView selectTabViewItemAtIndex:1];
|
|
|
|
var decoded = [self archived:tabView];
|
|
|
|
[self assert:@"two" equals:[[decoded selectedTabViewItem] identifier]];
|
|
[self assertNotNull:[[[decoded selectedTabViewItem] view] superview]];
|
|
|
|
[decoded selectTabViewItemAtIndex:0];
|
|
|
|
[self assert:@"one" equals:[[decoded selectedTabViewItem] identifier]];
|
|
[self assertNotNull:[[[decoded selectedTabViewItem] view] superview]];
|
|
}
|
|
|
|
- (void)testADecodedSplitViewMovesItsDivider
|
|
{
|
|
var splitView = [[CPSplitView alloc] initWithFrame:CGRectMake(0.0, 0.0, 200.0, 100.0)];
|
|
|
|
[splitView setVertical:YES];
|
|
[splitView addSubview:[[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 95.0, 100.0)]];
|
|
[splitView addSubview:[[CPView alloc] initWithFrame:CGRectMake(100.0, 0.0, 95.0, 100.0)]];
|
|
|
|
var decoded = [self archived:splitView];
|
|
|
|
[decoded setPosition:60.0 ofDividerAtIndex:0];
|
|
|
|
[self assert:60.0 equals:CGRectGetWidth([[decoded arrangedSubviews][0] frame])];
|
|
}
|
|
|
|
- (void)testADecodedScrollViewScrollsItsDocument
|
|
{
|
|
var scrollView = [[CPScrollView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
|
|
|
|
[scrollView setDocumentView:[[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 300.0, 300.0)]];
|
|
|
|
var decoded = [self archived:scrollView];
|
|
|
|
[[decoded documentView] scrollPoint:CGPointMake(0.0, 50.0)];
|
|
|
|
[self assert:50.0 equals:CGRectGetMinY([decoded documentVisibleRect])];
|
|
}
|
|
|
|
- (void)testADecodedBoxAcceptsANewContentView
|
|
{
|
|
var box = [[CPBox alloc] initWithFrame:CGRectMake(0.0, 0.0, 200.0, 100.0)];
|
|
|
|
[box setContentView:[[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)]];
|
|
|
|
var decoded = [self archived:box],
|
|
newContentView = [[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 60.0, 60.0)];
|
|
|
|
[decoded setContentView:newContentView];
|
|
|
|
[self assert:newContentView equals:[decoded contentView]];
|
|
[self assertNotNull:[newContentView superview]];
|
|
}
|
|
|
|
- (void)testADecodedAccordionKeepsACollapsedItem
|
|
{
|
|
var accordion = [[CPAccordionView alloc] initWithFrame:CGRectMake(0.0, 0.0, 200.0, 200.0)],
|
|
item = [[CPAccordionViewItem alloc] initWithIdentifier:@"item"];
|
|
|
|
[item setLabel:@"Item"];
|
|
[item setView:[[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 50.0)]];
|
|
[accordion addItem:item];
|
|
[accordion collapseItemAtIndex:0];
|
|
|
|
var decoded = [self archived:accordion];
|
|
|
|
[self assertFalse:[[decoded expandedItemIndexes] containsIndex:0]];
|
|
|
|
[decoded toggleItemAtIndex:0];
|
|
|
|
[self assertTrue:[[decoded expandedItemIndexes] containsIndex:0]];
|
|
}
|
|
|
|
- (void)testADecodedStackViewAcceptsAnArrangedSubview
|
|
{
|
|
var stackView = [[CPStackView alloc] initWithFrame:CGRectMake(0.0, 0.0, 200.0, 200.0)];
|
|
|
|
[stackView addArrangedSubview:[[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)]];
|
|
|
|
var decoded = [self archived:stackView];
|
|
|
|
[decoded addArrangedSubview:[[CPView alloc] initWithFrame:CGRectMake(0.0, 0.0, 50.0, 50.0)]];
|
|
|
|
[self assert:2 equals:[[decoded arrangedSubviews] count]];
|
|
}
|
|
|
|
- (void)testADecodedTokenFieldEndsWithItsTokens
|
|
{
|
|
var field = [[CPTokenField alloc] initWithFrame:CGRectMake(0.0, 0.0, 200.0, 30.0)];
|
|
|
|
[field setObjectValue:[@"One", @"Two"]];
|
|
|
|
var decoded = [self archived:field];
|
|
|
|
[self assert:[@"One", @"Two"] equals:[decoded objectValue]];
|
|
|
|
[decoded setObjectValue:[@"Two"]];
|
|
|
|
[self assert:[@"Two"] equals:[decoded objectValue]];
|
|
}
|
|
|
|
- (void)testADecodedTableReadsItsDataSource
|
|
{
|
|
var tableView = [[CPTableView alloc] initWithFrame:CGRectMake(0.0, 0.0, 200.0, 200.0)];
|
|
|
|
[tableView addTableColumn:[[CPTableColumn alloc] initWithIdentifier:@"Column"]];
|
|
[tableView setDataSource:[[_CPDecodedTableDataSource alloc] init]];
|
|
|
|
var decoded = [self archived:tableView];
|
|
|
|
[decoded reloadData];
|
|
|
|
[self assert:3 equals:[decoded numberOfRows]];
|
|
}
|
|
|
|
- (void)testADecodedOutlineReadsItsDataSource
|
|
{
|
|
var outlineView = [[CPOutlineView alloc] initWithFrame:CGRectMake(0.0, 0.0, 200.0, 200.0)],
|
|
column = [[CPTableColumn alloc] initWithIdentifier:@"Column"];
|
|
|
|
[outlineView addTableColumn:column];
|
|
[outlineView setOutlineTableColumn:column];
|
|
[outlineView setDataSource:[[_CPDecodedOutlineDataSource alloc] init]];
|
|
|
|
var decoded = [self archived:outlineView];
|
|
|
|
[decoded reloadData];
|
|
|
|
[self assert:1 equals:[decoded numberOfRows]];
|
|
[self assert:@"Parent" equals:[decoded itemAtRow:0]];
|
|
[self assert:@"Parent" equals:[decoded _objectValueForTableColumn:[decoded outlineTableColumn] row:0 useCache:NO]];
|
|
|
|
[decoded expandItem:@"Parent"];
|
|
|
|
[self assert:2 equals:[decoded numberOfRows]];
|
|
}
|
|
|
|
@end
|