Skip to content

Commit cbd16d0

Browse files
committed
Expose chart state to UI tests and extend UITests
Add a lightweight accessibility probe view (basic-chart.state) and a launch-argument flag (-UITestExposeChartState) to BasicChartVC to expose a serialized chart state for UI tests. Implement serialization and update the probe on draw/refresh/controls changes. Update UITests: rename/enhance a basic-chart test to exercise stacking, corner, and switch controls, add helpers to launch with the new flag, read/parse the JSON state, and assert state changes reliably. This enables more comprehensive, deterministic UI tests for chart configuration.
1 parent 885e529 commit cbd16d0

2 files changed

Lines changed: 213 additions & 6 deletions

File tree

AAChartKitDemo/Demo/AAChartModel/ViewController/BasicChartVC.m

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ @interface BasicChartVC ()<AAChartViewEventDelegate>
3939
@property (nonatomic, strong) UIStackView *mainStackView;
4040
@property (nonatomic, strong) UIStackView *segmentedControlsStackView;
4141
@property (nonatomic, strong) UIStackView *switchesStackView;
42+
@property (nonatomic, strong) UIView *chartStateProbeView;
43+
@property (nonatomic, assign) BOOL shouldExposeUITestChartState;
4244

4345
@end
4446

@@ -62,6 +64,7 @@ - (void)viewDidLoad {
6264
[super viewDidLoad];
6365
self.view.backgroundColor = [AAEasyTool colorWithHexString:@"#4b2b7f"];
6466
self.view.accessibilityIdentifier = @"basic-chart.view";
67+
self.shouldExposeUITestChartState = [[[NSProcessInfo processInfo] arguments] containsObject:@"-UITestExposeChartState"];
6568

6669
AAChartType chartType = [self configureTheChartType];
6770
self.title = [NSString stringWithFormat:@"%@ chart",chartType];
@@ -79,6 +82,7 @@ - (void)drawChart {
7982
AAChartType chartType = [self configureTheChartType];
8083
[self setupAAChartViewWithChartType:chartType];
8184
[_aaChartView aa_drawChartWithChartModel:_aaChartModel];
85+
[self updateUITestChartStateProbe];
8286
}
8387

8488
- (void)setupAAChartView {
@@ -105,11 +109,97 @@ - (void)setupAAChartView {
105109
} else {
106110
// Fallback on earlier versions
107111
}
108-
112+
113+
[self setupUITestChartStateProbeIfNeeded];
109114
[self setupChartViewEventHandlers];
110115
[self setupMainStackView];
111116
}
112117

118+
- (void)setupUITestChartStateProbeIfNeeded {
119+
if (!self.shouldExposeUITestChartState || _chartStateProbeView != nil) {
120+
return;
121+
}
122+
123+
UIView *stateProbeView = [[UIView alloc] init];
124+
stateProbeView.translatesAutoresizingMaskIntoConstraints = NO;
125+
stateProbeView.backgroundColor = UIColor.clearColor;
126+
stateProbeView.userInteractionEnabled = NO;
127+
stateProbeView.isAccessibilityElement = YES;
128+
stateProbeView.accessibilityIdentifier = @"basic-chart.state";
129+
[self.view addSubview:stateProbeView];
130+
[NSLayoutConstraint activateConstraints:@[
131+
[stateProbeView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
132+
[stateProbeView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
133+
[stateProbeView.widthAnchor constraintEqualToConstant:1],
134+
[stateProbeView.heightAnchor constraintEqualToConstant:1],
135+
]];
136+
_chartStateProbeView = stateProbeView;
137+
}
138+
139+
- (void)updateUITestChartStateProbe {
140+
if (!self.shouldExposeUITestChartState) {
141+
return;
142+
}
143+
144+
[self setupUITestChartStateProbeIfNeeded];
145+
_chartStateProbeView.accessibilityValue = [self serializedUITestChartState];
146+
}
147+
148+
- (NSString *)serializedUITestChartState {
149+
NSDictionary<NSString *, id> *state = [self currentUITestChartState];
150+
NSArray<NSString *> *orderedKeys = @[
151+
@"chartType",
152+
@"stacking",
153+
@"borderRadius",
154+
@"xAxisReversed",
155+
@"yAxisReversed",
156+
@"inverted",
157+
@"polar",
158+
@"dataLabelsEnabled",
159+
];
160+
NSMutableArray<NSString *> *pairs = [NSMutableArray arrayWithCapacity:orderedKeys.count];
161+
for (NSString *key in orderedKeys) {
162+
id value = state[key];
163+
NSString *serializedValue = [self serializedUITestChartStateValue:value];
164+
[pairs addObject:[NSString stringWithFormat:@"\"%@\":%@", key, serializedValue]];
165+
}
166+
return [NSString stringWithFormat:@"{%@}", [pairs componentsJoinedByString:@","]];
167+
}
168+
169+
- (NSDictionary<NSString *, id> *)currentUITestChartState {
170+
NSString *chartType = _aaChartModel.chartType ?: [self configureTheChartType];
171+
NSString *stacking = [self normalizedUITestStackingValue:_aaChartModel.stacking];
172+
NSNumber *borderRadius = _aaChartModel.borderRadius ?: @0;
173+
174+
return @{
175+
@"chartType" : chartType ?: @"",
176+
@"stacking" : stacking,
177+
@"borderRadius" : borderRadius,
178+
@"xAxisReversed" : @(_aaChartModel.xAxisReversed),
179+
@"yAxisReversed" : @(_aaChartModel.yAxisReversed),
180+
@"inverted" : @(_aaChartModel.inverted),
181+
@"polar" : @(_aaChartModel.polar),
182+
@"dataLabelsEnabled" : @(_aaChartModel.dataLabelsEnabled),
183+
};
184+
}
185+
186+
- (NSString *)normalizedUITestStackingValue:(NSString *)stacking {
187+
return stacking.length > 0 ? stacking : @"false";
188+
}
189+
190+
- (NSString *)serializedUITestChartStateValue:(id)value {
191+
if ([value isKindOfClass:[NSString class]]) {
192+
NSString *escapedString = [(NSString *)value stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
193+
return [NSString stringWithFormat:@"\"%@\"", escapedString];
194+
}
195+
196+
if (CFGetTypeID((__bridge CFTypeRef)value) == CFBooleanGetTypeID()) {
197+
return [value boolValue] ? @"true" : @"false";
198+
}
199+
200+
return [value stringValue];
201+
}
202+
113203
- (void)setupMainStackView {
114204
// 创建主 StackView
115205
_mainStackView = [[UIStackView alloc] init];
@@ -449,6 +539,7 @@ - (void)switchViewClicked:(UISwitch *)switchView {
449539

450540
- (void)refreshTheChartView {
451541
[_aaChartView aa_refreshChartWithChartModel:_aaChartModel];
542+
[self updateUITestChartStateProbe];
452543
}
453544

454545
- (void)setUpTheNextTypeChartButton {
@@ -494,6 +585,8 @@ - (void)monitorTap {
494585
if (_chartType == BasicChartVCChartTypeScatter) {
495586
_chartType = -1;//重新开始
496587
}
588+
589+
[self updateUITestChartStateProbe];
497590
}
498591

499592
- (BOOL)isHairPhone {

AAChartKitDemoUITests/AAChartKitUITests.m

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,11 @@ - (void)setUp {
4444
self.continueAfterFailure = NO;
4545
}
4646

47-
- (void)testBasicChartVCColumnLayout {
48-
XCUIApplication *app = [self aa_launchBasicChartWithType:0];
47+
- (void)testBasicChartVCColumnComprehensiveInteractions {
48+
XCUIApplication *app = [self aa_launchBasicChartWithType:0 exposeChartState:YES];
4949

5050
XCTAssertTrue([app.webViews[@"basic-chart.chart-view"] waitForExistenceWithTimeout:5.0]);
51+
XCTAssertTrue([app.otherElements[@"basic-chart.state"] waitForExistenceWithTimeout:5.0]);
5152

5253
XCUIElement *stackingControl = app.segmentedControls[@"basic-chart.segmented.0"];
5354
XCUIElement *cornerControl = app.segmentedControls[@"basic-chart.segmented.1"];
@@ -57,9 +58,57 @@ - (void)testBasicChartVCColumnLayout {
5758
XCTAssertTrue(nextTypeButton.exists);
5859
XCTAssertEqual(stackingControl.buttons.count, 3);
5960
XCTAssertEqual(cornerControl.buttons.count, 3);
60-
61+
XCTAssertTrue(app.switches[@"basic-chart.switch.0"].exists);
62+
XCTAssertTrue(app.switches[@"basic-chart.switch.1"].exists);
63+
XCTAssertTrue(app.switches[@"basic-chart.switch.2"].exists);
64+
XCTAssertTrue(app.switches[@"basic-chart.switch.3"].exists);
6165
XCTAssertTrue(app.switches[@"basic-chart.switch.4"].exists);
6266
XCTAssertFalse(app.switches[@"basic-chart.switch.5"].exists);
67+
68+
[self aa_assertChartStateInApp:app matches:@{
69+
@"chartType": @"column",
70+
@"stacking": @"false",
71+
@"borderRadius": @0,
72+
@"xAxisReversed": @NO,
73+
@"yAxisReversed": @NO,
74+
@"inverted": @NO,
75+
@"polar": @NO,
76+
@"dataLabelsEnabled": @NO,
77+
}];
78+
79+
[stackingControl.buttons[@"Normal stacking"] tap];
80+
[self aa_assertChartStateInApp:app matches:@{@"stacking": @"normal"}];
81+
82+
[stackingControl.buttons[@"Percent stacking"] tap];
83+
[self aa_assertChartStateInApp:app matches:@{@"stacking": @"percent"}];
84+
85+
[stackingControl.buttons[@"No stacking"] tap];
86+
[self aa_assertChartStateInApp:app matches:@{@"stacking": @"false"}];
87+
88+
[cornerControl.buttons[@"Rounded corners"] tap];
89+
[self aa_assertChartStateInApp:app matches:@{@"borderRadius": @10}];
90+
91+
[cornerControl.buttons[@"Wedge"] tap];
92+
[self aa_assertChartStateInApp:app matches:@{@"borderRadius": @100}];
93+
94+
[cornerControl.buttons[@"Square corners"] tap];
95+
[self aa_assertChartStateInApp:app matches:@{@"borderRadius": @0}];
96+
97+
[self aa_assertSwitchWithIdentifier:@"basic-chart.switch.0"
98+
togglesStateKey:@"xAxisReversed"
99+
inApp:app];
100+
[self aa_assertSwitchWithIdentifier:@"basic-chart.switch.1"
101+
togglesStateKey:@"yAxisReversed"
102+
inApp:app];
103+
[self aa_assertSwitchWithIdentifier:@"basic-chart.switch.2"
104+
togglesStateKey:@"inverted"
105+
inApp:app];
106+
[self aa_assertSwitchWithIdentifier:@"basic-chart.switch.3"
107+
togglesStateKey:@"polar"
108+
inApp:app];
109+
[self aa_assertSwitchWithIdentifier:@"basic-chart.switch.4"
110+
togglesStateKey:@"dataLabelsEnabled"
111+
inApp:app];
63112
}
64113

65114
- (void)testBasicChartVCLineInteractions {
@@ -86,13 +135,78 @@ - (void)testBasicChartVCLineInteractions {
86135
}
87136

88137
- (XCUIApplication *)aa_launchBasicChartWithType:(NSInteger)chartType {
138+
return [self aa_launchBasicChartWithType:chartType exposeChartState:NO];
139+
}
140+
141+
- (XCUIApplication *)aa_launchBasicChartWithType:(NSInteger)chartType exposeChartState:(BOOL)exposeChartState {
89142
self.app = [[XCUIApplication alloc] init];
90-
self.app.launchArguments = @[
143+
NSMutableArray<NSString *> *launchArguments = [NSMutableArray arrayWithArray:@[
91144
@"-UITestBasicChartType",
92145
[NSString stringWithFormat:@"%ld", (long)chartType],
93-
];
146+
]];
147+
if (exposeChartState) {
148+
[launchArguments addObject:@"-UITestExposeChartState"];
149+
}
150+
self.app.launchArguments = launchArguments;
94151
[self.app launch];
95152
return self.app;
96153
}
97154

155+
- (void)aa_assertSwitchWithIdentifier:(NSString *)identifier
156+
togglesStateKey:(NSString *)stateKey
157+
inApp:(XCUIApplication *)app {
158+
XCUIElement *switchElement = app.switches[identifier];
159+
XCTAssertTrue(switchElement.exists);
160+
161+
[switchElement tap];
162+
[self aa_assertChartStateInApp:app matches:@{stateKey: @YES}];
163+
164+
[switchElement tap];
165+
[self aa_assertChartStateInApp:app matches:@{stateKey: @NO}];
166+
}
167+
168+
- (void)aa_assertChartStateInApp:(XCUIApplication *)app matches:(NSDictionary<NSString *, id> *)expectedState {
169+
NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:5.0];
170+
NSDictionary<NSString *, id> *lastState = nil;
171+
172+
while (deadline.timeIntervalSinceNow > 0) {
173+
lastState = [self aa_chartStateFromApp:app];
174+
if ([self aa_chartState:lastState containsExpectedEntries:expectedState]) {
175+
return;
176+
}
177+
178+
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
179+
}
180+
181+
XCTFail(@"Expected chart state %@, got %@", expectedState, lastState);
182+
}
183+
184+
- (NSDictionary<NSString *, id> *)aa_chartStateFromApp:(XCUIApplication *)app {
185+
XCUIElement *stateElement = app.otherElements[@"basic-chart.state"];
186+
XCTAssertTrue(stateElement.exists);
187+
188+
id rawValue = stateElement.value;
189+
XCTAssertTrue([rawValue isKindOfClass:[NSString class]]);
190+
191+
NSError *error;
192+
NSData *jsonData = [(NSString *)rawValue dataUsingEncoding:NSUTF8StringEncoding];
193+
NSDictionary<NSString *, id> *state = [NSJSONSerialization JSONObjectWithData:jsonData
194+
options:0
195+
error:&error];
196+
XCTAssertNil(error);
197+
XCTAssertTrue([state isKindOfClass:[NSDictionary class]]);
198+
return state;
199+
}
200+
201+
- (BOOL)aa_chartState:(NSDictionary<NSString *, id> *)state
202+
containsExpectedEntries:(NSDictionary<NSString *, id> *)expectedState {
203+
for (NSString *key in expectedState) {
204+
if (![state[key] isEqual:expectedState[key]]) {
205+
return NO;
206+
}
207+
}
208+
209+
return YES;
210+
}
211+
98212
@end

0 commit comments

Comments
 (0)