<commit_before>
@import <AppKit/CPKeyValueBinding.j>
@import <AppKit/CPTextField.j>

// FIXME These imports are needed to prevent the following two errors:
// error=ReferenceError: Can't find variable: CPTopVerticalTextAlignment
// error=ReferenceError: Can't find variable: CPImageLeft
// CPControl uses these but in ojtest runs the dependencies don't
// get imported for some reason.
@import <AppKit/CPButton.j>
@import <AppKit/_CPImageAndTextView.j>

@import <AppKit/CPClipView.j>
@import <AppKit/CPTableView.j>
@import <AppKit/CPArrayController.j>

@implementation CPKeyValueBindingTest : OJTestCase
{
    id      FOO;
}

- (void)testExposingBindings
{
    [BindingTester exposeBinding:@"foo"];
    [BindingTester exposeBinding:@"bar"];
    [CPObject exposeBinding:@"zoo"];

    [self assert:["foo", "bar", "zoo"] equals:[[BindingTester new] exposedBindings]];
}

- (void)testBindTo
{
    var binder = [BindingTester new];
    binder.cheese = "orange";

    [CPKeyValueBindingTest exposeBinding:@"FOO"];

    [self bind:"FOO" toObject:binder withKeyPath:"cheese" options:nil];

    [binder setCheese:@"banana"];

    [self assertTrue:[self valueForKey:@"FOO"] === "banana" message:"Bound value should have been updated to banana, was " + FOO];
}

- (void)testBindOptions
{

    var bindTesterA = [BindingTestWithBool new],
        bindTesterB = [BindingTestWithBool new];

    [bindTesterB bind:@"stringValue" toObject:bindTesterA withKeyPath:@"stringValue" options:[CPDictionary dictionary]];
    [self assert:nil equals:[bindTesterA stringValue] message:"initial A value unchanged"];
    [self assert:nil equals:[bindTesterB stringValue] message:"initial B value unchanged"];

    [bindTesterA setStringValue:@"My string"];
    [self assert:@"My string" equals:[bindTesterA stringValue] message:"A value set"];
    [self assert:@"My string" equals:[bindTesterB stringValue] message:"B value updated"];

    [bindTesterA setStringValue:nil];
    [self assert:nil equals:[bindTesterA stringValue] message:"A value reset"];
    [self assert:nil equals:[bindTesterB stringValue] message:"B value updated with nil"];

    [bindTesterA unbind:@"stringValue"];

    [bindTesterB bind:@"boolValue" toObject:bindTesterA withKeyPath:@"stringValue" options:[CPDictionary dictionaryWithObject:CPIsNotNilTransformerName forKey:CPValueTransformerNameBindingOption]];
    [bindTesterB setBoolValue:YES];
    [bindTesterA setStringValue:nil];
    [self assert:nil equals:[bindTesterA stringValue] message:"A value reset"];
    [self assert:NO equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterB setBoolValue:NO];
    [bindTesterA setStringValue:@"My string"];
    [self assert:@"My string" equals:[bindTesterA stringValue] message:"A value updated"];
    [self assert:YES equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterB setBoolValue:NO];
    [bindTesterA setStringValue:@""];
    [self assert:@"" equals:[bindTesterA stringValue] message:"A value updated"];
    [self assert:YES equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterA unbind:@"boolValue"];

    [bindTesterB bind:@"boolValue" toObject:bindTesterA withKeyPath:@"stringValue" options:[CPDictionary dictionaryWithObject:CPIsNilTransformerName forKey:CPValueTransformerNameBindingOption]];
    [bindTesterB setBoolValue:NO];
    [bindTesterA setStringValue:nil];
    [self assert:nil equals:[bindTesterA stringValue] message:"A value reset"];
    [self assert:YES equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterB setBoolValue:YES];
    [bindTesterA setStringValue:@"My string"];
    [self assert:@"My string" equals:[bindTesterA stringValue] message:"A value updated"];
    [self assert:NO equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterB setBoolValue:YES];
    [bindTesterA setStringValue:@""];
    [self assert:@"" equals:[bindTesterA stringValue] message:"A value updated"];
    [self assert:NO equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterA unbind:@"boolValue"];


    [bindTesterB bind:@"stringValue" toObject:bindTesterA withKeyPath:@"stringValue" options:[CPDictionary dictionaryWithObject:@"placeholder" forKey:CPNullPlaceholderBindingOption]];

    [bindTesterA setStringValue:@""];
    [self assert:@"" equals:[bindTesterA stringValue] message:"A value set (placeholder)"];
    [self assert:@"" equals:[bindTesterB stringValue] message:"B value updated (placeholder)"];

    [bindTesterA setStringValue:@"My string"];
    [self assert:@"My string" equals:[bindTesterA stringValue] message:"A value set (placeholder)"];
    [self assert:@"My string" equals:[bindTesterB stringValue] message:"B value updated (placeholder)"];

    [bindTesterA setStringValue:nil];
    [self assert:nil equals:[bindTesterA stringValue] message:"A value reset (placeholder)"];
    [self assert:@"placeholder" equals:[bindTesterB stringValue] message:"B value updated (placeholder)"];
}

- (void)testControl
{
    FOO = "bingo";

    var binder = [BindingTester new];
    binder.cheese = "yellow";

    var control = [[CPTextField alloc] init];
    [control setStringValue:@"brown"];

    [CPControl exposeBinding:CPValueBinding];
    [[self class] exposeBinding:@"FOO"];

    //[control addObserver:self forKeyPath:CPValueBinding options:nil context:"testControl"];

    [control bind:CPValueBinding toObject:self withKeyPath:@"FOO" options:nil];
    // Should this even work? It's a two way binding. Anyhow, it currently crashes
    // objj disrupting the other unit tests, so commenting it out for now.
    //[self bind:@"FOO" toObject:control withKeyPath:CPValueBinding options:nil];

    [control setStringValue:@"banana"];
    [control setStringValue:@"grapefruit"];

    [self setValue:@"BAR" forKey:@"FOO"];

    [self assert:FOO equals:[control stringValue]];

    [control setStringValue:@"pina colada"];

    //[self assert:FOO equals:[control stringValue]];
}

- (void)testTableColumn
{
    var tableView = [CPTableView new],
        tableColumn = [[CPTableColumn alloc] initWithIdentifier:"A Column"],
        arrayController = [CPArrayController new];

    [tableView addTableColumn:tableColumn];

    content = [
        [AccessCounter counterWithValueA:"1" valueB:"2"],
        [AccessCounter counterWithValueA:"3" valueB:"4"],
        [AccessCounter counterWithValueA:"5" valueB:"6"],
    ];
    [arrayController setContent:content];

    [tableColumn bind:@"value" toObject:arrayController withKeyPath:@"arrangedObjects.valueA" options:nil];

    // Reset these if they were read during initialization.
    for (var i = 0; i < [content count]; i++)
        [content[i] setAccesses:0];
    var testView = [DataViewTester new];
    [tableColumn prepareDataView:testView forRow:0];
    [self assert:'1' equals:testView.lastValue];
    [self assert:'value' equals:testView.lastKey];

    [tableColumn prepareDataView:testView forRow:1];
    [self assert:'3' equals:testView.lastValue];
    [self assert:'value' equals:testView.lastKey];

    // Test that CPTableColumn is optimized to only read one value per row.
    [self assert:0 equals:[content[2] accesses] message:"row 2 used "+[content[2] accesses]+" accesses but was never prepared"];
    [self assert:1 equals:[content[0] accesses] message:"row 0 used "+[content[0] accesses]+" accesses to prepare"];
    [self assert:1 equals:[content[1] accesses] message:"row 1 used "+[content[1] accesses]+" accesses to prepare"];

    // Try the case where a key path is not used.
    content = ["plain", "old", "space crystals"];
    [tableColumn bind:@"value" toObject:arrayController withKeyPath:@"arrangedObjects" options:nil];
    [arrayController setContent:content];

    [tableColumn prepareDataView:testView forRow:1];
    [self assert:'old' equals:testView.lastValue];
    [self assert:'value' equals:testView.lastKey];
}

- (void)testTextField
{
    var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
    [textField setPlaceholderString:@"cheese"];


    content = [
        [BindingTester testerWithCheese:@"yellow"],
        [BindingTester testerWithCheese:@"green"]
    ];
    arrayController = [[CPArrayController alloc] initWithContent:content];

    [arrayController setSelectionIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 2)]];

    var options = [CPDictionary dictionaryWithJSObject:{CPMultipleValuesPlaceholderBindingOption:@"Multiple Values"}];
    [textField bind:@"value" toObject:arrayController withKeyPath:@"selection.cheese" options:options];

    [self assert:@"Multiple Values" equals:[textField placeholderString]];
<commit_msg>Unit test exposing a new bug: setting a binding on a text field causes it to lose its placeholder string.<commit_after>
@import <AppKit/CPKeyValueBinding.j>
@import <AppKit/CPTextField.j>

// FIXME These imports are needed to prevent the following two errors:
// error=ReferenceError: Can't find variable: CPTopVerticalTextAlignment
// error=ReferenceError: Can't find variable: CPImageLeft
// CPControl uses these but in ojtest runs the dependencies don't
// get imported for some reason.
@import <AppKit/CPButton.j>
@import <AppKit/_CPImageAndTextView.j>

@import <AppKit/CPClipView.j>
@import <AppKit/CPTableView.j>
@import <AppKit/CPArrayController.j>

@implementation CPKeyValueBindingTest : OJTestCase
{
    id      FOO;
}

- (void)testExposingBindings
{
    [BindingTester exposeBinding:@"foo"];
    [BindingTester exposeBinding:@"bar"];
    [CPObject exposeBinding:@"zoo"];

    [self assert:["foo", "bar", "zoo"] equals:[[BindingTester new] exposedBindings]];
}

- (void)testBindTo
{
    var binder = [BindingTester new];
    binder.cheese = "orange";

    [CPKeyValueBindingTest exposeBinding:@"FOO"];

    [self bind:"FOO" toObject:binder withKeyPath:"cheese" options:nil];

    [binder setCheese:@"banana"];

    [self assertTrue:[self valueForKey:@"FOO"] === "banana" message:"Bound value should have been updated to banana, was " + FOO];
}

- (void)testBindOptions
{

    var bindTesterA = [BindingTestWithBool new],
        bindTesterB = [BindingTestWithBool new];

    [bindTesterB bind:@"stringValue" toObject:bindTesterA withKeyPath:@"stringValue" options:[CPDictionary dictionary]];
    [self assert:nil equals:[bindTesterA stringValue] message:"initial A value unchanged"];
    [self assert:nil equals:[bindTesterB stringValue] message:"initial B value unchanged"];

    [bindTesterA setStringValue:@"My string"];
    [self assert:@"My string" equals:[bindTesterA stringValue] message:"A value set"];
    [self assert:@"My string" equals:[bindTesterB stringValue] message:"B value updated"];

    [bindTesterA setStringValue:nil];
    [self assert:nil equals:[bindTesterA stringValue] message:"A value reset"];
    [self assert:nil equals:[bindTesterB stringValue] message:"B value updated with nil"];

    [bindTesterA unbind:@"stringValue"];

    [bindTesterB bind:@"boolValue" toObject:bindTesterA withKeyPath:@"stringValue" options:[CPDictionary dictionaryWithObject:CPIsNotNilTransformerName forKey:CPValueTransformerNameBindingOption]];
    [bindTesterB setBoolValue:YES];
    [bindTesterA setStringValue:nil];
    [self assert:nil equals:[bindTesterA stringValue] message:"A value reset"];
    [self assert:NO equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterB setBoolValue:NO];
    [bindTesterA setStringValue:@"My string"];
    [self assert:@"My string" equals:[bindTesterA stringValue] message:"A value updated"];
    [self assert:YES equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterB setBoolValue:NO];
    [bindTesterA setStringValue:@""];
    [self assert:@"" equals:[bindTesterA stringValue] message:"A value updated"];
    [self assert:YES equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterA unbind:@"boolValue"];

    [bindTesterB bind:@"boolValue" toObject:bindTesterA withKeyPath:@"stringValue" options:[CPDictionary dictionaryWithObject:CPIsNilTransformerName forKey:CPValueTransformerNameBindingOption]];
    [bindTesterB setBoolValue:NO];
    [bindTesterA setStringValue:nil];
    [self assert:nil equals:[bindTesterA stringValue] message:"A value reset"];
    [self assert:YES equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterB setBoolValue:YES];
    [bindTesterA setStringValue:@"My string"];
    [self assert:@"My string" equals:[bindTesterA stringValue] message:"A value updated"];
    [self assert:NO equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterB setBoolValue:YES];
    [bindTesterA setStringValue:@""];
    [self assert:@"" equals:[bindTesterA stringValue] message:"A value updated"];
    [self assert:NO equals:[bindTesterB boolValue] message:"B value updated"];

    [bindTesterA unbind:@"boolValue"];


    [bindTesterB bind:@"stringValue" toObject:bindTesterA withKeyPath:@"stringValue" options:[CPDictionary dictionaryWithObject:@"placeholder" forKey:CPNullPlaceholderBindingOption]];

    [bindTesterA setStringValue:@""];
    [self assert:@"" equals:[bindTesterA stringValue] message:"A value set (placeholder)"];
    [self assert:@"" equals:[bindTesterB stringValue] message:"B value updated (placeholder)"];

    [bindTesterA setStringValue:@"My string"];
    [self assert:@"My string" equals:[bindTesterA stringValue] message:"A value set (placeholder)"];
    [self assert:@"My string" equals:[bindTesterB stringValue] message:"B value updated (placeholder)"];

    [bindTesterA setStringValue:nil];
    [self assert:nil equals:[bindTesterA stringValue] message:"A value reset (placeholder)"];
    [self assert:@"placeholder" equals:[bindTesterB stringValue] message:"B value updated (placeholder)"];
}

- (void)testControl
{
    FOO = "bingo";

    var binder = [BindingTester new];
    binder.cheese = "yellow";

    var control = [[CPTextField alloc] init];
    [control setStringValue:@"brown"];

    [CPControl exposeBinding:CPValueBinding];
    [[self class] exposeBinding:@"FOO"];

    //[control addObserver:self forKeyPath:CPValueBinding options:nil context:"testControl"];

    [control bind:CPValueBinding toObject:self withKeyPath:@"FOO" options:nil];
    // Should this even work? It's a two way binding. Anyhow, it currently crashes
    // objj disrupting the other unit tests, so commenting it out for now.
    //[self bind:@"FOO" toObject:control withKeyPath:CPValueBinding options:nil];

    [control setStringValue:@"banana"];
    [control setStringValue:@"grapefruit"];

    [self setValue:@"BAR" forKey:@"FOO"];

    [self assert:FOO equals:[control stringValue]];

    [control setStringValue:@"pina colada"];

    //[self assert:FOO equals:[control stringValue]];
}

- (void)testTableColumn
{
    var tableView = [CPTableView new],
        tableColumn = [[CPTableColumn alloc] initWithIdentifier:"A Column"],
        arrayController = [CPArrayController new];

    [tableView addTableColumn:tableColumn];

    content = [
        [AccessCounter counterWithValueA:"1" valueB:"2"],
        [AccessCounter counterWithValueA:"3" valueB:"4"],
        [AccessCounter counterWithValueA:"5" valueB:"6"],
    ];
    [arrayController setContent:content];

    [tableColumn bind:@"value" toObject:arrayController withKeyPath:@"arrangedObjects.valueA" options:nil];

    // Reset these if they were read during initialization.
    for (var i = 0; i < [content count]; i++)
        [content[i] setAccesses:0];
    var testView = [DataViewTester new];
    [tableColumn prepareDataView:testView forRow:0];
    [self assert:'1' equals:testView.lastValue];
    [self assert:'value' equals:testView.lastKey];

    [tableColumn prepareDataView:testView forRow:1];
    [self assert:'3' equals:testView.lastValue];
    [self assert:'value' equals:testView.lastKey];

    // Test that CPTableColumn is optimized to only read one value per row.
    [self assert:0 equals:[content[2] accesses] message:"row 2 used "+[content[2] accesses]+" accesses but was never prepared"];
    [self assert:1 equals:[content[0] accesses] message:"row 0 used "+[content[0] accesses]+" accesses to prepare"];
    [self assert:1 equals:[content[1] accesses] message:"row 1 used "+[content[1] accesses]+" accesses to prepare"];

    // Try the case where a key path is not used.
    content = ["plain", "old", "space crystals"];
    [tableColumn bind:@"value" toObject:arrayController withKeyPath:@"arrangedObjects" options:nil];
    [arrayController setContent:content];

    [tableColumn prepareDataView:testView forRow:1];
    [self assert:'old' equals:testView.lastValue];
    [self assert:'value' equals:testView.lastKey];
}

- (void)testTextField
{
    var textField = [[CPTextField alloc] initWithFrame:CGRectMakeZero()];
    [textField setPlaceholderString:@"cheese"];

    // Establish a random binding.
    [textField bind:@"hidden" toObject:self withKeyPath:@"FOO" options:nil];
    [self assert:@"cheese" equals:[textField placeholderString] message:"placeholder should not be cleared when a binding is established"];

    content = [
        [BindingTester testerWithCheese:@"yellow"],
        [BindingTester testerWithCheese:@"green"]
    ];
    arrayController = [[CPArrayController alloc] initWithContent:content];

    [arrayController setSelectionIndexes:[CPIndexSet indexSetWithIndexesInRange:CPMakeRange(0, 2)]];

    var options = [CPDictionary dictionaryWithJSObject:{CPMultipleValuesPlaceholderBindingOption:@"Multiple Values"}];
    [textField bind:@"value" toObject:arrayController withKeyPath:@"selection.cheese" options:options];

    [self assert:@"Multiple Values" equals:[textField placeholderString]];

{'fraction_non_alphanumeric': 0.1291473219340958, 'fraction_numerical': 0.0030574113916883704, 'mean_word_length': 5.474706744868035, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6183964', 'n_tokens_mistral': 5499, 'n_tokens_neox': 5044, 'n_words': 1304}